-
Notifications
You must be signed in to change notification settings - Fork 386
feat(clerk-js,shared): Align useCheckout
with future hooks
#6761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 3a82f64 The changes in this PR will be included in the next version bump. This PR includes changesets to release 22 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughIntroduces a signal-based checkout flow replacing the previous instance/manager model. Adds CheckoutFuture, createSignals, and related types; updates Clerk/React integration to return CheckoutSignalValue; refactors UI to consume errors/fetchStatus from signals; modifies component typings; removes legacy manager and tests; adds a changeset for minor bumps. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor App
participant Hook as useCheckout()
participant ReactCtx as Clerk Instance (Context)
participant ClerkJS as clerk-js (__experimental_checkout)
participant Inst as createCheckoutInstance (cache)
participant Signals as createSignals
participant Future as CheckoutFuture
participant Billing as Clerk.billing
App->>Hook: useCheckout({ planId, planPeriod, for })
Hook->>ReactCtx: get clerk instance
Hook->>ClerkJS: __experimental_checkout(options)
alt Cached by key
ClerkJS->>Inst: createCheckoutInstance(...)
Inst-->>Hook: cached computed signal (CheckoutSignalValue)
else Not cached
ClerkJS->>Inst: createCheckoutInstance(...)
Inst->>Signals: createSignals()
Inst->>Future: new CheckoutFuture(signals, config)
Inst-->>Hook: computed signal (CheckoutSignalValue)
end
App->>Hook: subscribe via useSyncExternalStore
App->>Hook: read CheckoutSignalValue
App->>Future: checkout.start()
Future->>Billing: start checkout (async)
Billing-->>Future: result or error
Future->>Signals: update resource/error/fetchStatus
App->>Future: checkout.confirm(params)
Future->>Billing: confirm (async)
Billing-->>Future: result or error
Future->>Signals: update resource/error/fetchStatus
sequenceDiagram
autonumber
actor App
participant Iso as isomorphicClerk.__experimental_checkout
participant SP as StateProxy.checkoutSignal
participant CJ as clerkjs.__experimental_checkout
App->>Iso: __experimental_checkout(options)
alt clerk loaded and clerkjs present
Iso->>CJ: delegate(options)
CJ-->>App: CheckoutSignalValue
else not loaded
Iso->>SP: fallback(options)
SP-->>App: Gated CheckoutSignalValue proxy
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
✨ Finishing touches
🧪 Generate unit tests
Comment |
cf9af90
to
492f569
Compare
@clerk/agent-toolkit
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/dev-cli
@clerk/elements
@clerk/clerk-expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/clerk-react
@clerk/react-router
@clerk/remix
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/themes
@clerk/types
@clerk/upgrade
@clerk/vue
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/shared/src/react/commerce.tsx (2)
257-260
: Bug: amount uses || and breaks when totalDueNow.amount is 0If totalDueNow.amount is 0, the expression falls back to grandTotal erroneously. Use nullish coalescing.
- amount: checkout.totals.totalDueNow?.amount || checkout.totals.grandTotal.amount, + amount: checkout.totals.totalDueNow?.amount ?? checkout.totals.grandTotal.amount,
299-323
: Type contract: return async functions in not-ready branchsubmit/reset must return Promise types. Throwing sync breaks declared signature.
const throwLibsMissingError = () => { throw new Error( 'Clerk: Unable to submit, Stripe libraries are not yet loaded. Be sure to check `isFormReady` before calling `submit`.', ); }; + +const throwLibsMissingErrorAsync = async (): Promise<never> => { + throwLibsMissingError(); +}; @@ if (!isProviderReady) { return { - submit: throwLibsMissingError, - reset: throwLibsMissingError, + submit: throwLibsMissingErrorAsync, + reset: throwLibsMissingErrorAsync, isFormReady: false, provider: undefined, isProviderReady: false, }; }Also applies to: 371-389
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (1)
61-81
: Stale memo: missingerrors
in dependency array.The memo reads
errors
but only depends onfetchStatus
. Includeerrors
(orerrors.global
) to recompute when errors change.- }, [fetchStatus]); + }, [fetchStatus, errors, errors?.global]);Optionally, depend on the derived codes:
- }, [fetchStatus]); + }, [fetchStatus, errors?.global?.map(e => e.code).join(',')]);
🧹 Nitpick comments (23)
packages/types/src/utils.ts (2)
44-49
: Constrain ForceNull to objectsPrevents accidental use with primitives and narrows intent without breaking current usages.
-export type ForceNull<T> = { +export type ForceNull<T extends object> = { [K in keyof T]: null; };
127-129
: Avoid any in type-level function detectionPrefer unknown to align with “no any” guideline; keeps identical behavior.
-export type RemoveFunctions<T extends object> = { - [K in keyof T as T[K] extends (...args: any[]) => any ? never : K]: T[K]; -}; +export type RemoveFunctions<T extends object> = { + [K in keyof T as T[K] extends (...args: unknown[]) => unknown ? never : K]: T[K]; +};packages/shared/src/react/commerce.tsx (1)
85-93
: Optional: log initialization failures for Stripe payment sourceSilent catch makes diagnosing setup issues hard. Consider logging at debug level.
- initializePaymentSource().catch(() => { - // ignore errors - }); + initializePaymentSource().catch((e) => { + // Optional: surface in dev without spamming prod logs + if (process.env.NODE_ENV !== 'production') { + console.debug('initializePaymentSource failed', e); + } + });packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx (1)
167-175
: Remove no-op useRef; rely on effectuseRef(() => setter(text)) never runs; it only stores a function. Keep the effect; tighten types.
-const useSetAndSync = (text: LocalizationKey, setter: (a: any) => void) => { - useRef(() => { - setter(text); - }); - - useEffect(() => { - setter(text); - }, [text, setter]); -}; +const useSetAndSync = (text: LocalizationKey, setter: (v: LocalizationKey) => void) => { + useEffect(() => { + setter(text); + }, [text, setter]); +};packages/react/src/isomorphicClerk.ts (1)
743-747
: Add explicit return types for public API methods and keep style consistent.Per guidelines, explicitly annotate return types for public APIs. Also consider using the same if/else style used elsewhere for consistency.
Apply:
- __experimental_checkout = (...args: Parameters<Clerk['__experimental_checkout']>) => { + __experimental_checkout = ( + ...args: Parameters<Clerk['__experimental_checkout']> + ): ReturnType<Clerk['__experimental_checkout']> => { return this.loaded && this.clerkjs ? this.clerkjs.__experimental_checkout(...args) : this.#stateProxy.checkoutSignal(...args); }; - __experimental_checkoutV2 = (...args: Parameters<Clerk['__experimental_checkoutV2']>) => { + __experimental_checkoutV2 = ( + ...args: Parameters<Clerk['__experimental_checkoutV2']> + ): ReturnType<Clerk['__experimental_checkoutV2']> => { return this.loaded && this.clerkjs ? this.clerkjs.__experimental_checkoutV2(...args) : this.#stateProxy.checkoutSignalV2(...args); };packages/clerk-js/src/ui/components/Checkout/parts.tsx (3)
46-50
: Avoid@ts-expect-error
by narrowing error metadata.Replace the suppression with a safe meta extractor.
- const _error = errors?.global?.find(e => e.code === 'invalid_plan_change'); - // @ts-expect-error - meta is not yet defined - return _error?.meta?.plan; + const _error = errors?.global?.find(e => e.code === 'invalid_plan_change'); + const meta = + _error && typeof _error === 'object' && 'meta' in (_error as any) + ? (( _error as any ).meta as { plan?: any }) + : undefined; + return meta?.plan;Consider formalizing the meta type in
packages/types
for this code, to remove casts entirely.
52-56
: Same metadata narrowing forisPlanUpgradePossible
.- const _error = errors?.global?.find(e => e.code === 'invalid_plan_change'); - // @ts-expect-error - meta is not yet defined - return _error?.meta?.isPlanUpgradePossible || false; + const _error = errors?.global?.find(e => e.code === 'invalid_plan_change'); + const meta = + _error && typeof _error === 'object' && 'meta' in (_error as any) + ? (( _error as any ).meta as { isPlanUpgradePossible?: boolean }) + : undefined; + return meta?.isPlanUpgradePossible ?? false;
119-121
: Handle possible start() errors.
checkout.start()
returns an object with{ error }
. Consider logging or surfacing it.- onSuccess={() => void checkout.start()} + onSuccess={async () => { + const { error } = await checkout.start(); + if (error) { + // TODO: surface via card context or alert + console.error(error); + } + }}packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (3)
143-146
: Avoid throwing in render path; gate UI instead.Throwing here bubbles to error boundaries; prefer gating the elements that require confirmation.
- if (checkout.status !== 'needs_confirmation') { - throw new Error('Checkout not found'); - } + if (checkout.status !== 'needs_confirmation') { + return { + payWithExistingPaymentSource: () => Promise.resolve(), + addPaymentSourceAndPay: () => Promise.resolve(), + payWithTestCard: () => Promise.resolve(), + }; + }
151-159
: Type-safe error handling without@ts-expect-error
.
handleError
already branches on known error types; no need for a suppression.- const { error } = await checkout.confirm(params); - - if (error) { - // @ts-expect-error - error is not an Error - handleError(error, [], card.setError); + const { error } = await checkout.confirm(params); + if (error) { + handleError(error as unknown, [], card.setError); } else { onSubscriptionComplete?.(); }
369-371
: Variable naming mismatch with behavior.
shouldDefaultBeUsed
renders the selector when true. Rename for clarity.- const shouldDefaultBeUsed = totalDueNow.amount === 0 || !freeTrialEndsAt; + const showPaymentSourceSelector = totalDueNow.amount === 0 || !freeTrialEndsAt;And update usages accordingly.
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (1)
66-78
: Error code mapping is fine. Consider early-return for perf/readability.Small readability tweak:
- if (errors.global) { + if (errors.global?.length) {packages/clerk-js/src/core/modules/checkout/instance.ts (1)
110-114
: Type assertion could hide type mismatches.The type assertion on lines 112-113 could mask potential type mismatches between the cache entry and the expected return type.
Consider using a type guard or narrowing the type more safely:
- if (cache.has(checkoutKey)) { - return ( - cache.get(checkoutKey) as { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> } - ).signals.computedSignal(); - } + const cached = cache.get(checkoutKey); + if (cached) { + return cached.signals.computedSignal(); + }packages/shared/src/react/hooks/useCheckout.ts (3)
195-203
: Commented-out code should be removed.Lines 186-193 contain commented-out code that appears to be an alternative implementation. This should be removed for code cleanliness.
- // const signal = useCallback(() => { - // return clerk.__experimental_checkoutV2({ planId, planPeriod, for: forOrganization }); - // }, [ - // // user?.id, organization?.id, - // planId, - // planPeriod, - // forOrganization, - // ]); - const signal = useMemo(() => {
98-104
: Commented-out dependencies in useMemo.Lines 100 and 199 have commented-out dependencies (
user?.id, organization?.id
). If these are not needed, remove them completely. If they might be needed, add a TODO comment explaining why.const manager = useMemo(() => { return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization }); - }, [ - // user?.id, organization?.id, - planId, - planPeriod, - forOrganization, - ]); + }, [planId, planPeriod, forOrganization, clerk]);Also for line 197-202:
const signal = useMemo(() => { return () => clerk.__experimental_checkoutV2({ planId, planPeriod, for: forOrganization }); - }, [ - // user?.id, organization?.id, - planId, - planPeriod, - forOrganization, - ]); + }, [planId, planPeriod, forOrganization, clerk]);
106-120
: Large block of commented-out code should be removed.Lines 106-120 contain a large block of commented-out code. This should be removed for code cleanliness.
Remove the entire commented-out block from lines 106-120.
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (1)
25-43
: Potential memory leak with requestAnimationFrame.While the cleanup function is present, there's a potential issue where
animationRef.current
could be overwritten before the previous animation is cancelled if the effect runs multiple times quickly.useEffect(() => { if (!canHover) { return; } + // Cancel any existing animation before starting a new one + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } const animate = () => { setCurrentPosition(prev => { const amt = 0.15; const x = lerp(prev.x, positionX, amt); const y = lerp(prev.y, positionY, amt); return { x, y }; }); animationRef.current = requestAnimationFrame(animate); }; animationRef.current = requestAnimationFrame(animate); return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [positionX, positionY, canHover]);packages/types/src/commerce.ts (1)
1404-1410
: Consider using a const assertion for status values.The discriminated union pattern is good, but the status values could be defined as a const to ensure consistency.
Consider defining the status values as a const:
const CHECKOUT_STATUS = { NEEDS_INITIALIZATION: 'needs_initialization', NEEDS_CONFIRMATION: 'needs_confirmation', COMPLETED: 'completed' } as const; type CheckoutStatus = typeof CHECKOUT_STATUS[keyof typeof CHECKOUT_STATUS];This would ensure consistency across the codebase and make refactoring easier.
packages/react/src/stateProxy.ts (1)
128-179
: Remove large block of commented-out code.Lines 128-179 contain a large block of commented-out code. This should be removed for code cleanliness.
Remove the entire commented-out
buildCheckoutProxy
implementation from lines 128-179.packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
133-164
: Avoidany
in request body; keep types intact
params
is alreadyConfirmCheckoutParams
. Prefer passing the typed value (orunknown
) instead ofany
.Apply this diff:
- body: params as any, + body: params as unknown,
254-285
: Simplify and normalize error parsingCurrent logic toggles between
null
and[]
forraw/global
. Consider consistent shape for easier consumers.Apply this diff:
function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] { - const parsedErrors: CheckoutSignalValue['errors'] = { - raw: null, - global: null, - }; - - if (!error) { - return parsedErrors; - } - - if (!isClerkAPIResponseError(error)) { - parsedErrors.raw = []; - parsedErrors.global = []; - return parsedErrors; - } - - error.errors.forEach(error => { - if (parsedErrors.raw) { - parsedErrors.raw.push(error); - } else { - parsedErrors.raw = [error]; - } - - if (parsedErrors.global) { - parsedErrors.global.push(error); - } else { - parsedErrors.global = [error]; - } - }); - - return parsedErrors; + if (!error) { + return { raw: null, global: null }; + } + if (!isClerkAPIResponseError(error)) { + return { raw: [], global: [] }; + } + const list = [...error.errors]; + return { raw: list, global: [...list] }; }
287-305
: Public API JSDoc is missing
createSignals
andCheckoutFuture
are part of the public surface for Checkout V2. Add concise JSDoc with@experimental
, shape, and lifecycle notes.I can draft JSDoc blocks aligned with the types package if you want.
Also applies to: 306-363
21-93
: Remove large commented-out blocksDead/commented code adds noise and confuses readers. Please delete these blocks instead of commenting out.
Also applies to: 167-253
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (19)
.changeset/angry-maps-mix.md
(1 hunks)packages/clerk-js/src/core/clerk.ts
(4 hunks)packages/clerk-js/src/core/modules/checkout/instance.ts
(3 hunks)packages/clerk-js/src/core/resources/CommerceCheckout.ts
(3 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
(3 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
(5 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
(4 hunks)packages/clerk-js/src/ui/components/Checkout/index.tsx
(1 hunks)packages/clerk-js/src/ui/components/Checkout/parts.tsx
(4 hunks)packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
(1 hunks)packages/react/src/experimental.ts
(1 hunks)packages/react/src/isomorphicClerk.ts
(1 hunks)packages/react/src/stateProxy.ts
(6 hunks)packages/shared/src/react/commerce.tsx
(2 hunks)packages/shared/src/react/hooks/index.ts
(1 hunks)packages/shared/src/react/hooks/useCheckout.ts
(4 hunks)packages/types/src/clerk.ts
(4 hunks)packages/types/src/commerce.ts
(2 hunks)packages/types/src/utils.ts
(2 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{js,jsx,ts,tsx}
: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels
Files:
packages/shared/src/react/hooks/index.ts
packages/react/src/experimental.ts
packages/shared/src/react/commerce.tsx
packages/react/src/stateProxy.ts
packages/react/src/isomorphicClerk.ts
packages/clerk-js/src/ui/components/Checkout/parts.tsx
packages/clerk-js/src/ui/components/Checkout/index.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
packages/clerk-js/src/core/clerk.ts
packages/types/src/commerce.ts
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/shared/src/react/hooks/index.ts
packages/react/src/experimental.ts
packages/shared/src/react/commerce.tsx
packages/react/src/stateProxy.ts
packages/react/src/isomorphicClerk.ts
packages/clerk-js/src/ui/components/Checkout/parts.tsx
packages/clerk-js/src/ui/components/Checkout/index.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
packages/clerk-js/src/core/clerk.ts
packages/types/src/commerce.ts
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/shared/src/react/hooks/index.ts
packages/react/src/experimental.ts
packages/shared/src/react/commerce.tsx
packages/react/src/stateProxy.ts
packages/react/src/isomorphicClerk.ts
packages/clerk-js/src/ui/components/Checkout/parts.tsx
packages/clerk-js/src/ui/components/Checkout/index.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
packages/clerk-js/src/core/clerk.ts
packages/types/src/commerce.ts
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
packages/**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Packages should export TypeScript types alongside runtime code
Files:
packages/shared/src/react/hooks/index.ts
packages/react/src/experimental.ts
packages/shared/src/react/commerce.tsx
packages/react/src/stateProxy.ts
packages/react/src/isomorphicClerk.ts
packages/clerk-js/src/ui/components/Checkout/parts.tsx
packages/clerk-js/src/ui/components/Checkout/index.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
packages/clerk-js/src/core/clerk.ts
packages/types/src/commerce.ts
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
packages/**/index.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use tree-shaking friendly exports
Files:
packages/shared/src/react/hooks/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
**/*.{ts,tsx}
: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidany
type - preferunknown
when type is uncertain, then narrow with type guards
Useinterface
for object shapes that might be extended
Usetype
for unions, primitives, and computed types
Preferreadonly
properties for immutable data structures
Useprivate
for internal implementation details
Useprotected
for inheritance hierarchies
Usepublic
explicitly for clarity in public APIs
Preferreadonly
for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Useconst assertions
for literal types:as const
Usesatisfies
operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports:import type { ... } from ...
Noany
types without justification
Proper error handling with typed errors
Consistent use ofreadonly
for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)
Files:
packages/shared/src/react/hooks/index.ts
packages/react/src/experimental.ts
packages/shared/src/react/commerce.tsx
packages/react/src/stateProxy.ts
packages/react/src/isomorphicClerk.ts
packages/clerk-js/src/ui/components/Checkout/parts.tsx
packages/clerk-js/src/ui/components/Checkout/index.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
packages/clerk-js/src/core/clerk.ts
packages/types/src/commerce.ts
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
**/*.{js,ts,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.
Files:
packages/shared/src/react/hooks/index.ts
packages/react/src/experimental.ts
packages/shared/src/react/commerce.tsx
packages/react/src/stateProxy.ts
packages/react/src/isomorphicClerk.ts
packages/clerk-js/src/ui/components/Checkout/parts.tsx
packages/clerk-js/src/ui/components/Checkout/index.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
packages/clerk-js/src/core/clerk.ts
packages/types/src/commerce.ts
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
**/index.ts
📄 CodeRabbit inference engine (.cursor/rules/react.mdc)
Use index.ts files for clean imports but avoid deep barrel exports
Avoid barrel files (index.ts re-exports) as they can cause circular dependencies
Files:
packages/shared/src/react/hooks/index.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{jsx,tsx}
: Use error boundaries in React components
Minimize re-renders in React components
**/*.{jsx,tsx}
: Always use functional components with hooks instead of class components
Follow PascalCase naming for components:UserProfile
,NavigationMenu
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Use useState for simple state management
Use useReducer for complex state logic
Implement proper state initialization
Use proper state updates with callbacks
Implement proper state cleanup
Use Context API for theme/authentication
Implement proper state selectors
Use proper state normalization
Implement proper state persistence
Use React.memo for expensive components
Implement proper useCallback for handlers
Use proper useMemo for expensive computations
Implement proper virtualization for lists
Use proper code splitting with React.lazy
Implement proper cleanup in useEffect
Use proper refs for DOM access
Implement proper event listener cleanup
Use proper abort controllers for fetch
Implement proper subscription cleanup
Use proper HTML elements
Implement proper ARIA attributes
Use proper heading hierarchy
Implement proper form labels
Use proper button types
Implement proper focus management
Use proper keyboard shortcuts
Implement proper tab order
Use proper skip links
Implement proper focus traps
Implement proper error boundaries
Use proper error logging
Implement proper error recovery
Use proper error messages
Implement proper error fallbacks
Use proper form validation
Implement proper error states
Use proper error messages
Implement proper form submission
Use proper form reset
Use proper component naming
Implement proper file naming
Use proper prop naming
Implement proper...
Files:
packages/shared/src/react/commerce.tsx
packages/clerk-js/src/ui/components/Checkout/parts.tsx
packages/clerk-js/src/ui/components/Checkout/index.tsx
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/react.mdc)
**/*.tsx
: Use proper type definitions for props and state
Leverage TypeScript's type inference where possible
Use proper event types for handlers
Implement proper generic types for reusable components
Use proper type guards for conditional rendering
Files:
packages/shared/src/react/commerce.tsx
packages/clerk-js/src/ui/components/Checkout/parts.tsx
packages/clerk-js/src/ui/components/Checkout/index.tsx
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/clerk-js-ui.mdc)
packages/clerk-js/src/ui/**/*.{ts,tsx}
: Element descriptors should always be camelCase
Use element descriptors in UI components to enable consistent theming and styling via appearance.elements
Element descriptors should generate unique, stable CSS classes for theming
Element descriptors should handle state classes (e.g., cl-loading, cl-active, cl-error, cl-open) automatically based on component state
Do not render hard-coded values; all user-facing strings must be localized using provided localization methods
Use the useLocalizations hook and localizationKeys utility for all text and error messages
Use the styled system (sx prop, theme tokens, responsive values) for custom component styling
Use useCardState for card-level state, useFormState for form-level state, and useLoadingStatus for loading states
Always use handleError utility for API errors and use translateError for localized error messages
Use useFormControl for form field state, implement proper validation, and handle loading and error states in forms
Use localization keys for all form labels and placeholders
Use element descriptors for consistent styling and follow the theme token system
Use the Card and FormContainer patterns for consistent UI structure
Files:
packages/clerk-js/src/ui/components/Checkout/parts.tsx
packages/clerk-js/src/ui/components/Checkout/index.tsx
packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
.changeset/**
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Automated releases must use Changesets.
Files:
.changeset/angry-maps-mix.md
🧬 Code graph analysis (13)
packages/shared/src/react/commerce.tsx (2)
packages/types/src/commerce.ts (2)
CheckoutFutureResource
(1412-1427)CommerceCheckoutResource
(1195-1253)packages/shared/src/react/hooks/useCheckout.ts (1)
useCheckout
(78-165)
packages/react/src/stateProxy.ts (3)
packages/types/src/commerce.ts (3)
ForPayerType
(122-122)CommerceSubscriptionPlanPeriod
(139-139)CheckoutFutureResource
(1412-1427)packages/types/src/clerk.ts (3)
__experimental_CheckoutInstance
(129-136)NullableCheckoutSignal
(94-96)Clerk
(204-934)packages/clerk-js/src/core/clerk.ts (1)
Clerk
(201-2957)
packages/react/src/isomorphicClerk.ts (2)
packages/clerk-js/src/core/clerk.ts (1)
Clerk
(201-2957)packages/types/src/clerk.ts (1)
Clerk
(204-934)
packages/clerk-js/src/ui/components/Checkout/parts.tsx (2)
packages/shared/src/react/hooks/index.ts (1)
useCheckout
(16-16)packages/shared/src/react/hooks/useCheckout.ts (1)
useCheckout
(78-165)
packages/clerk-js/src/core/modules/checkout/instance.ts (2)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
CheckoutFuture
(306-362)createSignals
(287-304)packages/types/src/clerk.ts (2)
__experimental_CheckoutOptions
(113-117)NullableCheckoutSignal
(94-96)
packages/clerk-js/src/core/clerk.ts (1)
packages/types/src/clerk.ts (2)
__experimental_CheckoutOptions
(113-117)NullableCheckoutSignal
(94-96)
packages/types/src/commerce.ts (1)
packages/types/src/utils.ts (2)
RemoveFunctions
(127-129)ForceNull
(47-49)
packages/shared/src/react/hooks/useCheckout.ts (4)
packages/shared/src/react/contexts.tsx (2)
useCheckoutContext
(118-118)useClerkInstanceContext
(117-117)packages/clerk-js/src/ui/contexts/components/Checkout.ts (1)
useCheckoutContext
(10-37)packages/types/src/clerk.ts (1)
CheckoutSignalValue
(79-92)packages/clerk-js/src/core/modules/checkout/manager.ts (1)
subscribe
(127-132)
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (2)
packages/clerk-js/src/ui/customizables/index.ts (1)
Box
(15-15)packages/clerk-js/src/ui/customizables/elementDescriptors.ts (1)
descriptors
(568-568)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
FieldError
(7-20)packages/types/src/commerce.ts (1)
CheckoutFutureResource
(1412-1427)
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (3)
packages/react/src/stateProxy.ts (1)
checkout
(270-274)packages/shared/src/react/hooks/index.ts (1)
useCheckout
(16-16)packages/shared/src/react/hooks/useCheckout.ts (1)
useCheckout
(78-165)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (6)
packages/react/src/stateProxy.ts (1)
checkout
(270-274)packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
plan
(329-331)totals
(335-337)packages/types/src/commerce.ts (1)
ConfirmCheckoutParams
(1162-1188)packages/clerk-js/src/ui/utils/errorHandler.ts (1)
handleError
(69-91)packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
usePaymentMethods
(32-40)packages/shared/src/react/hooks/usePaymentMethods.tsx (1)
usePaymentMethods
(9-21)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
packages/types/src/json.ts (1)
CommerceCheckoutJSON
(880-895)packages/types/src/clerk.ts (2)
CheckoutSignalValue
(79-92)CheckoutSignal
(98-100)packages/types/src/commerce.ts (3)
CheckoutFutureResourceLax
(1429-1446)CreateCheckoutParams
(1136-1155)ConfirmCheckoutParams
(1162-1188)packages/clerk-js/src/utils/runAsyncResourceTask.ts (1)
runAsyncResourceTask
(8-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build Packages
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (16)
packages/clerk-js/src/ui/components/Checkout/index.tsx (1)
43-45
: Confirm status literal alignment ("generic_error") — confirmedCheckoutPage.FetchStatus declares/accepts 'generic_error' (packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx) and index.tsx uses it; underlying hook/types still use 'error' (packages/shared/src/react/hooks/useCheckout.ts, packages/types/src/clerk.ts) but CheckoutPage maps hook errors → 'generic_error', so no migration required.
packages/shared/src/react/hooks/index.ts (1)
17-18
: Verified: useCheckoutV2 exported and no barrel-induced cycleuseCheckout.ts defines and exports useCheckoutV2 and packages/shared/src/react/hooks/index.ts re-exports it; useCheckout imports useOrganization/useUser via relative files (not via the hooks barrel), so the barrel does not introduce a circular import.
packages/react/src/experimental.ts (1)
23-24
: LGTM: re-export wiring is correctNamed re-export keeps tree-shaking friendly surface and matches shared aliasing.
packages/react/src/isomorphicClerk.ts (1)
737-741
: Good gating fallback for v1 checkout.Delegating to ClerkJS when loaded and falling back to StateProxy is consistent with the rest of the class.
packages/clerk-js/src/ui/components/Checkout/parts.tsx (1)
1-1
: Hook migration looks correct.Importing the v2 hook under the existing alias preserves local usage.
packages/types/src/clerk.ts (5)
25-26
: LGTM on adding CheckoutFutureResource import.
58-58
: LGTM onFieldError
import for error modeling.
65-75
: Error model shape is sensible.Structured
raw
andglobal
arrays look good.
94-101
: Confirm intended return shape: is__experimental_checkoutV2
returning a function (signal) or a snapshot?You define
CheckoutSignal
, but__experimental_CheckoutFunctionV2
returnsNullableCheckoutSignal
(a value), leavingCheckoutSignal
unused. If the API is meant to return a callable signal, change the type:-type __experimental_CheckoutFunctionV2 = (options: __experimental_CheckoutOptions) => NullableCheckoutSignal; +type __experimental_CheckoutFunctionV2 = (options: __experimental_CheckoutOptions) => CheckoutSignal;Otherwise, remove
CheckoutSignal
to avoid confusion. Please verify across implementations.
928-934
: API surface addition looks good; ensure public JSDoc matches behavior.Docstring matches the experimental nature.
packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx (1)
12-17
: Double-check cleanup removal.
clear()
was removed from the cleanup. If v2 lifecycle no longer needs explicit clearing, fine; otherwise consider adding a v2-equivalent to avoid stale state between mounts.packages/clerk-js/src/core/modules/checkout/instance.ts (1)
92-125
: Missing explicit return type annotation.The function
createCheckoutInstanceV2
is missing an explicit return type annotation, which goes against the coding guidelines for TypeScript files.-function createCheckoutInstanceV2(clerk: Clerk, options: __experimental_CheckoutOptions): NullableCheckoutSignal { +function createCheckoutInstanceV2(clerk: Clerk, options: __experimental_CheckoutOptions): NullableCheckoutSignal {Actually, the return type is already specified. The issue is resolved.
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (1)
17-157
: LGTM! Well-structured component extraction.The extraction of the
SuccessRing
component improves code organization and reusability. The animation logic is properly encapsulated with appropriate cleanup.packages/types/src/commerce.ts (1)
1356-1402
: LGTM! Well-structured type definitions for V2 checkout.The new
CheckoutFutureProperties
interface and related types are properly documented and follow TypeScript best practices with appropriate use of utility types.packages/react/src/stateProxy.ts (1)
203-253
: Missing explicit return type annotations for private methods.Several private methods are missing explicit return type annotations, which goes against the coding guidelines.
Add explicit return types to the private methods:
- private gateListenerMethod<T extends object, K extends keyof T>( + private gateListenerMethod<T extends object, K extends keyof T>( getTarget: () => T, key: T[K] extends (...args: any[]) => void ? K : never, ): T[K] {Actually, the return type
T[K]
is already specified. The methods have proper return type annotations.packages/clerk-js/src/core/resources/CommerceCheckout.ts (1)
108-131
: Constructor now acceptsnull
: verify consumer assumptionsExposing an instance with many undefined fields until
start()
completes may break consumers that expect non-null values. Ensure all call sites read getters only afterstatus !== 'needs_initialization'
, or add guards/JSDoc clarifying lifecycle.
--- | ||
'@clerk/clerk-js': minor | ||
'@clerk/shared': minor | ||
'@clerk/clerk-react': minor | ||
'@clerk/types': minor | ||
--- | ||
|
||
wip |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Replace placeholder body with actionable release notes
The changeset message is "wip". Add a concise summary, migration notes, and experimental flags to produce useful release notes for consumers.
Apply:
-
-wip
+Introduce experimental Checkout V2 (signals-based).
+
+– Add __experimental_checkoutV2 (Clerk JS) and __experimental_useCheckoutV2 (React/shared).
+– UI: switch CheckoutPage.FetchStatus fallback to "generic_error".
+– Shared: PaymentElementProvider accepts CheckoutFutureResource.
+– Types: add NullableCheckoutSignal, CheckoutFutureResource, ForceNull, RemoveFunctions.
+
+Migration
+– Prefer __experimental_* V2 APIs; V1 remains available.
+– If you relied on "error" FetchStatus, handle "generic_error".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
--- | |
'@clerk/clerk-js': minor | |
'@clerk/shared': minor | |
'@clerk/clerk-react': minor | |
'@clerk/types': minor | |
--- | |
wip | |
--- | |
'@clerk/clerk-js': minor | |
'@clerk/shared': minor | |
'@clerk/clerk-react': minor | |
'@clerk/types': minor | |
--- | |
Introduce experimental Checkout V2 (signals-based). | |
– Add __experimental_checkoutV2 (Clerk JS) and __experimental_useCheckoutV2 (React/shared). | |
– UI: switch CheckoutPage.FetchStatus fallback to "generic_error". | |
– Shared: PaymentElementProvider accepts CheckoutFutureResource. | |
– Types: add NullableCheckoutSignal, CheckoutFutureResource, ForceNull, RemoveFunctions. | |
Migration | |
– Prefer __experimental_* V2 APIs; V1 remains available. | |
– If you relied on "error" FetchStatus, handle "generic_error". |
🤖 Prompt for AI Agents
In .changeset/angry-maps-mix.md lines 1-8 the body is a placeholder "wip" and
must be replaced with actionable release notes; update the file by replacing
"wip" with a concise summary of the changes (1–2 sentences), add any migration
notes or breaking changes steps if applicable, and list experimental flags or
opt-in notes under a short "notes" section so consumers know impact and how to
opt in; keep the existing YAML package header intact and ensure the body is
plain text with clear sections like Summary, Migration, and Experimental flags.
/** | ||
* Create a checkout instance with the given options | ||
*/ | ||
const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache not being cleared on user/organization changes.
The module-level cache persists across user sessions and organization changes, which could lead to stale data being returned when a user signs out and signs back in or switches organizations.
Add cache invalidation when the user or organization context changes. Consider moving the cache to a Clerk instance property or implementing a cache invalidation mechanism:
+// Clear cache when user or organization changes
+export function clearCheckoutCache() {
+ cache.clear();
+}
Additionally, consider adding lifecycle hooks to clear the cache when the user signs out or the active organization changes.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>(); | |
const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>(); | |
/** | |
* Clear the module-level checkout cache. | |
* | |
* Call this when the authenticated user or active organization changes to avoid | |
* returning stale checkout data across sessions. This should be invoked from | |
* higher-level lifecycle hooks (e.g. on sign-out or organization switch). | |
* | |
* @public | |
*/ | |
export function clearCheckoutCache(): void { | |
cache.clear(); | |
} |
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/modules/checkout/instance.ts around line 22, the
module-level cache Map is persisting across user sessions and org changes
causing stale data; replace it with an instance-scoped cache (attach the Map to
the Clerk instance or the checkout module instance) or implement explicit
invalidation: remove the module-level declaration, create the Map on the Clerk
instance, update all accesses to use this.instance.cache, and wire lifecycle
hooks/events (signOut, activeOrganization change) to call cache.clear(); ensure
type signatures remain correct and any tests or callers are updated to use the
instance-scoped cache.
const clerk = useClerkInstanceContext(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing dependency clerk
in useMemo hooks.
Both useCheckout
and useCheckoutV2
are missing clerk
in their dependency arrays for the useMemo
hooks. This could lead to stale closures if the clerk instance changes.
For useCheckout
(line 97-104):
const manager = useMemo(() => {
return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization });
- }, [planId, planPeriod, forOrganization]);
+ }, [planId, planPeriod, forOrganization, clerk]);
For useCheckoutV2
(line 195-202):
const signal = useMemo(() => {
return () => clerk.__experimental_checkoutV2({ planId, planPeriod, for: forOrganization });
- }, [planId, planPeriod, forOrganization]);
+ }, [planId, planPeriod, forOrganization, clerk]);
Also applies to: 184-185
🤖 Prompt for AI Agents
packages/shared/src/react/hooks/useCheckout.ts around lines 95-106 and 180-205:
Several useMemo hooks in useCheckout (approx. lines 97-104) and useCheckoutV2
(approx. lines 184-185 and 195-202) omit the clerk variable from their
dependency arrays, risking stale closures if the clerk instance changes; add
clerk to each affected useMemo dependency array (alongside existing deps), and
ensure any lint warnings are addressed by including any other values referenced
inside the memo callbacks so the dependency lists accurately reflect all
external values used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (5)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (5)
3-13
: Import missingCheckoutSignal
andNullableCheckoutSignal
types to properly type the computed signalThe
@ts-expect-error
comment on line 291 indicates thatCheckoutSignal
is not defined. Import the missing types from@clerk/types
and properly type the computed signal to remove the need for type suppression.Apply this diff to fix the type imports and usage:
import type { CheckoutFutureResourceLax, CheckoutSignalValue, + CheckoutSignal, + NullableCheckoutSignal, CommerceCheckoutJSON, CommerceCheckoutResource, CommerceCheckoutTotals, CommercePayerResource, CommerceSubscriptionPlanPeriod, ConfirmCheckoutParams, CreateCheckoutParams, } from '@clerk/types';
291-301
: Remove@ts-expect-error
and properly type the computed signalThe computed signal needs proper typing with the imported CheckoutSignal types. Also remove the redundant property shorthand and commented console.log.
Apply this diff:
- // @ts-expect-error - CheckoutSignal is not yet defined - const computedSignal: CheckoutSignal = computed(() => { + const computedSignal: CheckoutSignal = computed<NullableCheckoutSignal>(() => { const resource = resourceSignal().resource; const error = errorSignal().error; const fetchStatus = fetchSignal().status; - // console.log('computedSignal', resource, error, fetchStatus); - const errors = errorsToParsedErrors(error); - return { errors: errors, fetchStatus, checkout: resource }; + return { errors, fetchStatus, checkout: resource }; });
356-362
: Add null checks for billing client to prevent runtime errorsThe
start()
method doesn't validate thatCommerceCheckout.clerk.billing
exists before callingstartCheckout
. This could result in assigningundefined
tothis.resource
.Apply this diff to add proper validation:
async start(): Promise<{ error: unknown }> { return runAsyncResourceTask(this.signals, async () => { - const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout; - this.resource = checkout; + const billing = CommerceCheckout.clerk.billing; + if (!billing?.startCheckout) { + throw new Error('Checkout cannot start: billing client is not available.'); + } + const checkout = await billing.startCheckout(this.config); + if (!checkout) { + throw new Error('Checkout could not be initialized.'); + } + this.resource = checkout as CommerceCheckout; this.signals.resourceSignal({ resource: this }); }); }
364-369
: Validate resource initialization before confirming checkoutThe
confirm()
method should check that the resource has been initialized with a valid ID before attempting confirmation.Apply this diff:
async confirm(params: ConfirmCheckoutParams): Promise<{ error: unknown }> { return runAsyncResourceTask(this.signals, async () => { + if (!this.resource?.id) { + throw new Error('Cannot confirm checkout before initialization.'); + } await this.resource.confirm(params); this.signals.resourceSignal({ resource: this }); }); }
372-392
: Fix unbalanced signal batching inrunAsyncResourceTask
The current implementation has duplicate
startBatch()
calls and misplacedendBatch()
calls that can break signal batching invariants.Apply this diff to fix the batching:
async function runAsyncResourceTask<T>( signals: ReturnType<typeof createSignals>, task: () => Promise<T>, ): Promise<{ result?: T; error: unknown }> { startBatch(); signals.errorSignal({ error: null }); signals.fetchSignal({ status: 'fetching' }); endBatch(); - startBatch(); try { const result = await task(); return { result, error: null }; } catch (err) { signals.errorSignal({ error: err }); - endBatch(); return { error: err }; } finally { + startBatch(); signals.fetchSignal({ status: 'idle' }); - endBatch(); + endBatch(); } }
🧹 Nitpick comments (4)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
21-93
: Remove or extract commented-out code block to documentationLines 21-93 contain a large block of commented-out code. This appears to be legacy implementation or experimental code that should either be removed entirely or moved to documentation if it serves as a reference.
Consider removing this entire commented block as it adds unnecessary noise to the codebase and can cause confusion about which implementation is current.
167-233
: Remove additional commented-out code blockLines 167-233 contain another large block of commented experimental code. This should be removed to maintain code cleanliness.
254-285
: Simplify error transformation logic inerrorsToParsedErrors
The current implementation has redundant conditionals when building the arrays. The logic can be simplified.
Apply this diff to simplify the error transformation:
function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] { const parsedErrors: CheckoutSignalValue['errors'] = { raw: null, global: null, }; if (!error) { return parsedErrors; } if (!isClerkAPIResponseError(error)) { parsedErrors.raw = []; parsedErrors.global = []; return parsedErrors; } - error.errors.forEach(error => { - if (parsedErrors.raw) { - parsedErrors.raw.push(error); - } else { - parsedErrors.raw = [error]; - } - - if (parsedErrors.global) { - parsedErrors.global.push(error); - } else { - parsedErrors.global = [error]; - } - }); + parsedErrors.raw = [...error.errors]; + parsedErrors.global = [...error.errors]; return parsedErrors; }
352-354
: Consider adding null safety for planPeriodStartThe
planPeriodStart
getter returnsundefined
when not set, which might be inconsistent with other nullable properties.Consider returning
null
instead ofundefined
for consistency:get planPeriodStart() { - return this.resource.planPeriodStart; + return this.resource.planPeriodStart ?? null; }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
packages/clerk-js/src/core/resources/CommerceCheckout.ts
(3 hunks)packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
(10 hunks)packages/types/src/commerce.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/types/src/commerce.ts
🧰 Additional context used
📓 Path-based instructions (13)
packages/clerk-js/src/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/clerk-js-ui.mdc)
packages/clerk-js/src/ui/**/*.{ts,tsx}
: Element descriptors should always be camelCase
Use element descriptors in UI components to enable consistent theming and styling via appearance.elements
Element descriptors should generate unique, stable CSS classes for theming
Element descriptors should handle state classes (e.g., cl-loading, cl-active, cl-error, cl-open) automatically based on component state
Do not render hard-coded values; all user-facing strings must be localized using provided localization methods
Use the useLocalizations hook and localizationKeys utility for all text and error messages
Use the styled system (sx prop, theme tokens, responsive values) for custom component styling
Use useCardState for card-level state, useFormState for form-level state, and useLoadingStatus for loading states
Always use handleError utility for API errors and use translateError for localized error messages
Use useFormControl for form field state, implement proper validation, and handle loading and error states in forms
Use localization keys for all form labels and placeholders
Use element descriptors for consistent styling and follow the theme token system
Use the Card and FormContainer patterns for consistent UI structure
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{js,jsx,ts,tsx}
: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Packages should export TypeScript types alongside runtime code
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
**/*.{ts,tsx}
: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidany
type - preferunknown
when type is uncertain, then narrow with type guards
Useinterface
for object shapes that might be extended
Usetype
for unions, primitives, and computed types
Preferreadonly
properties for immutable data structures
Useprivate
for internal implementation details
Useprotected
for inheritance hierarchies
Usepublic
explicitly for clarity in public APIs
Preferreadonly
for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Useconst assertions
for literal types:as const
Usesatisfies
operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports:import type { ... } from ...
Noany
types without justification
Proper error handling with typed errors
Consistent use ofreadonly
for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{jsx,tsx}
: Use error boundaries in React components
Minimize re-renders in React components
**/*.{jsx,tsx}
: Always use functional components with hooks instead of class components
Follow PascalCase naming for components:UserProfile
,NavigationMenu
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Use useState for simple state management
Use useReducer for complex state logic
Implement proper state initialization
Use proper state updates with callbacks
Implement proper state cleanup
Use Context API for theme/authentication
Implement proper state selectors
Use proper state normalization
Implement proper state persistence
Use React.memo for expensive components
Implement proper useCallback for handlers
Use proper useMemo for expensive computations
Implement proper virtualization for lists
Use proper code splitting with React.lazy
Implement proper cleanup in useEffect
Use proper refs for DOM access
Implement proper event listener cleanup
Use proper abort controllers for fetch
Implement proper subscription cleanup
Use proper HTML elements
Implement proper ARIA attributes
Use proper heading hierarchy
Implement proper form labels
Use proper button types
Implement proper focus management
Use proper keyboard shortcuts
Implement proper tab order
Use proper skip links
Implement proper focus traps
Implement proper error boundaries
Use proper error logging
Implement proper error recovery
Use proper error messages
Implement proper error fallbacks
Use proper form validation
Implement proper error states
Use proper error messages
Implement proper form submission
Use proper form reset
Use proper component naming
Implement proper file naming
Use proper prop naming
Implement proper...
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
packages/**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Unit tests should use Jest or Vitest as the test runner.
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
packages/{clerk-js,elements,themes}/**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Visual regression testing should be performed for UI components.
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
**/*.{js,ts,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/react.mdc)
**/*.tsx
: Use proper type definitions for props and state
Leverage TypeScript's type inference where possible
Use proper event types for handlers
Implement proper generic types for reusable components
Use proper type guards for conditional rendering
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
**/*.test.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react.mdc)
**/*.test.{jsx,tsx}
: Use React Testing Library
Test component behavior, not implementation
Use proper test queries
Implement proper test isolation
Use proper test coverage
Test component interactions
Use proper test data
Implement proper test setup
Use proper test cleanup
Implement proper test assertions
Use proper test structure
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
**/__tests__/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/__tests__/**/*.{ts,tsx}
: Create type-safe test builders/factories
Use branded types for test isolation
Implement proper mock types that match interfaces
Files:
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
🧬 Code graph analysis (2)
packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx (1)
packages/clerk-js/src/ui/elements/Drawer.tsx (1)
Drawer
(561-570)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (5)
packages/types/src/json.ts (1)
CommerceCheckoutJSON
(880-895)packages/types/src/clerk.ts (2)
CheckoutSignalValue
(79-92)CheckoutSignal
(98-100)packages/types/src/commerce.ts (3)
CheckoutFutureResourceLax
(1429-1446)CreateCheckoutParams
(1136-1155)ConfirmCheckoutParams
(1162-1188)packages/clerk-js/src/utils/runAsyncResourceTask.ts (1)
runAsyncResourceTask
(8-30)packages/react/src/stateProxy.ts (1)
checkout
(270-274)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Build Packages
- GitHub Check: Formatting | Dedupe | Changeset
- GitHub Check: semgrep/ci
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (5)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
108-108
: Good: Constructor now accepts null for initializationThe change to allow
null
initialization aligns well with the new CheckoutFuture pattern that starts with an uninitialized state.
348-350
: Good addition: paymentSource getter with null fallbackThe addition of the
paymentSource
getter with a null fallback provides consistent API access.packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx (3)
34-34
: Good: Fixed hanging promises in testsThe change from unresolved promises to
mockResolvedValue({} as any)
properly prevents tests from hanging and ensures consistent test behavior.Also applies to: 70-70, 138-138, 170-170, 212-212, 238-238, 260-260, 284-284
479-569
: Well-structured test for subscription start date displayThe new test properly validates the display of subscription start dates for downgrades, with comprehensive mock data setup.
571-659
: Good test coverage for payment method displayThe test properly validates that payment method details (Visa ⋯ 4242) are displayed in the completed checkout stage.
|
||
const freeTrialEndsAt = new Date('2025-08-19'); | ||
|
||
console.log('fixtures.clerk.billing.startCheckout', fixtures.clerk.__experimental_checkoutV2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove debug console.log statement
Line 320 contains a console.log
statement that should be removed before merging.
- console.log('fixtures.clerk.billing.startCheckout', fixtures.clerk.__experimental_checkoutV2);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
console.log('fixtures.clerk.billing.startCheckout', fixtures.clerk.__experimental_checkoutV2); |
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
around line 320, there is a debug console.log statement logging
fixtures.clerk.__experimental_checkoutV2; remove that console.log entirely from
the test file so no debug output remains in the test suite, then run tests to
ensure nothing relies on the log.
pathRoot: '/', | ||
reload: jest.fn(), | ||
}, | ||
planPeriodStart: new Date('2025-08-19'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type inconsistency: planPeriodStart should be a number
According to the CommerceCheckoutJSON
type definition, plan_period_start
should be a Unix timestamp (number), not a Date object.
Apply this diff to fix the type:
- planPeriodStart: new Date('2025-08-19'),
+ planPeriodStart: new Date('2025-08-19').getTime() / 1000,
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
planPeriodStart: new Date('2025-08-19'), | |
planPeriodStart: new Date('2025-08-19').getTime() / 1000, |
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx
around line 547, planPeriodStart is currently set to a Date object which
violates the CommerceCheckoutJSON type that requires a Unix timestamp number;
replace the Date with a numeric Unix timestamp (seconds) — e.g., use
Math.floor(new Date('2025-08-19').getTime() / 1000) or an equivalent numeric
literal so planPeriodStart is a number, not a Date.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
2-14
: Remove ts-expect-error; import and use proper CheckoutSignal types
computedSignal
is suppressed andCheckoutSignal
isn’t imported. ImportCheckoutSignal
andNullableCheckoutSignal
, type the computed, and drop the redundanterrors: errors
.import type { CheckoutFutureResourceLax, CheckoutSignalValue, + CheckoutSignal, + NullableCheckoutSignal, CommerceCheckoutJSON, CommerceCheckoutResource, CommerceCheckoutTotals, CommercePayerResource, CommerceSubscriptionPlanPeriod, ConfirmCheckoutParams, CreateCheckoutParams, } from '@clerk/types'; import { computed, endBatch, signal, startBatch } from 'alien-signals'; @@ export const createSignals = () => { const resourceSignal = signal<{ resource: CheckoutFuture | null }>({ resource: null }); const errorSignal = signal<{ error: unknown }>({ error: null }); const fetchSignal = signal<{ status: 'idle' | 'fetching' }>({ status: 'idle' }); - // @ts-expect-error - CheckoutSignal is not yet defined - const computedSignal: CheckoutSignal = computed(() => { + const computedSignal: CheckoutSignal = computed<NullableCheckoutSignal>(() => { const resource = resourceSignal().resource; const error = errorSignal().error; const fetchStatus = fetchSignal().status; - - // console.log('computedSignal', resource, error, fetchStatus); - const errors = errorsToParsedErrors(error); - return { errors: errors, fetchStatus, checkout: resource }; + return { errors, fetchStatus, checkout: resource }; }); return { resourceSignal, errorSignal, fetchSignal, computedSignal }; };Also applies to: 287-304
357-369
: Guard missing billing client and falsy checkout before assigning to resourceWithout a billing client or if it returns a falsy value,
this.resource
becomesundefined
, breaking follow-ups.async start(): Promise<{ error: unknown }> { return this.runAsyncResourceTask( 'start', async () => { - const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout; - this.resource = checkout; + const billing = CommerceCheckout.clerk.billing; + if (!billing?.startCheckout) { + throw new Error('Checkout cannot start: billing client is not available.'); + } + const checkout = await billing.startCheckout(this.config); + if (!checkout) { + throw new Error('Checkout could not be initialized.'); + } + this.resource = checkout as CommerceCheckout; }, () => { this.resource = new CommerceCheckout(null); this.signals.resourceSignal({ resource: this }); }, ); }
371-375
: Prevent confirm before initialization (missing resource.id/payer)
this.resource
may lack an id/payer beforestart()
, leading to invalid paths or runtime errors.async confirm(params: ConfirmCheckoutParams): Promise<{ error: unknown }> { - return this.runAsyncResourceTask('confirm', async () => { - await this.resource.confirm(params); - }); + return this.runAsyncResourceTask('confirm', async () => { + if (!this.resource?.id) { + throw new Error('Cannot confirm checkout before initialization.'); + } + await this.resource.confirm(params); + }); }
382-414
: Fix unbalanced batching (double endBatch on catch path)Current flow calls
endBatch()
in both catch and finally for the samestartBatch()
, breaking batching invariants.const operationPromise = (async () => { startBatch(); signals.errorSignal({ error: null }); signals.fetchSignal({ status: 'fetching' }); // signals.resourceSignal({ resource: null }); beforeTask?.(); endBatch(); - startBatch(); try { + startBatch(); await task(); signals.resourceSignal({ resource: resource }); - return { error: null }; - } catch (err) { - signals.errorSignal({ error: err }); - endBatch(); - return { error: err }; - } finally { - pendingOperations.delete(operationType); - signals.fetchSignal({ status: 'idle' }); endBatch(); + return { error: null }; + } catch (err) { + startBatch(); + signals.errorSignal({ error: err }); + signals.fetchSignal({ status: 'idle' }); + endBatch(); + return { error: err }; + } finally { + pendingOperations.delete(operationType); + // fetch status already set in try/catch; + // no batching needed here } })();
🧹 Nitpick comments (4)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
133-151
: Retry comment doesn’t match configWith
initialDelay: 2000
,maxDelayBetweenRetries: 2000
,factor: 1.1
, delays are ~2s (capped), not “2s, 4s, 6s, 8s”. Update the comment.- // This will retry up to 3 times with an increasing delay - // It retries at 2s, 4s, 6s and 8s + // Retries up to ~3–4 times with ~2s delay (capped by maxDelayBetweenRetries)
167-253
: Remove large commented-out legacy blocksThese commented classes/functions add noise and risk drift. Delete or move to a design doc.
377-386
: Narrow operationType to a unionConstrain to 'start' | 'confirm' for safer dedupe semantics.
- private runAsyncResourceTask<T>(operationType: string, task: () => Promise<T>, beforeTask?: () => void) { + private runAsyncResourceTask<T>(operationType: 'start' | 'confirm', task: () => Promise<T>, beforeTask?: () => void) { return createRunAsyncResourceTask(this, this.signals, this.pendingOperations)(operationType, task, beforeTask); } @@ -): <T>(operationType: string, task: () => Promise<T>, beforeTask?: () => void) => Promise<{ error: unknown }> { +): <T>(operationType: 'start' | 'confirm', task: () => Promise<T>, beforeTask?: () => void) => Promise<{ error: unknown }> {
287-304
: Add JSDoc and explicit return types for public APIs
createSignals
andCheckoutFuture
are exported; add JSDoc and explicit return types to meet project guidelines.Also applies to: 306-380
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
packages/clerk-js/src/core/resources/CommerceCheckout.ts
(3 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{js,jsx,ts,tsx}
: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Packages should export TypeScript types alongside runtime code
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
**/*.{ts,tsx}
: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidany
type - preferunknown
when type is uncertain, then narrow with type guards
Useinterface
for object shapes that might be extended
Usetype
for unions, primitives, and computed types
Preferreadonly
properties for immutable data structures
Useprivate
for internal implementation details
Useprotected
for inheritance hierarchies
Usepublic
explicitly for clarity in public APIs
Preferreadonly
for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Useconst assertions
for literal types:as const
Usesatisfies
operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports:import type { ... } from ...
Noany
types without justification
Proper error handling with typed errors
Consistent use ofreadonly
for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
**/*.{js,ts,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
🧬 Code graph analysis (1)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)
packages/types/src/json.ts (1)
CommerceCheckoutJSON
(880-895)packages/types/src/clerk.ts (2)
CheckoutSignalValue
(79-92)CheckoutSignal
(98-100)packages/types/src/commerce.ts (3)
CheckoutFutureResourceLax
(1429-1446)CreateCheckoutParams
(1136-1155)ConfirmCheckoutParams
(1162-1188)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
- GitHub Check: Integration Tests (quickstart, chrome)
- GitHub Check: Integration Tests (vue, chrome)
- GitHub Check: Integration Tests (machine, chrome)
- GitHub Check: Integration Tests (custom, chrome)
- GitHub Check: Integration Tests (express, chrome)
- GitHub Check: Integration Tests (tanstack-react-router, chrome)
- GitHub Check: Integration Tests (react-router, chrome)
- GitHub Check: Integration Tests (elements, chrome)
- GitHub Check: Integration Tests (expo-web, chrome)
- GitHub Check: Integration Tests (sessions, chrome)
- GitHub Check: Unit Tests (18, --filter=@clerk/astro --filter=@clerk/backend --filter=@clerk/express --filter=@c...
- GitHub Check: Publish with pkg-pr-new
- GitHub Check: Unit Tests (22, **)
- GitHub Check: Static analysis
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/clerk-js/src/core/modules/checkout/instance.ts (1)
22-55
: Adopt instance-scoped cache in create function.Switch to per-instance map; add invalidation hook elsewhere (on signOut/org change).
-function createCheckoutInstance(clerk: Clerk, options: __experimental_CheckoutOptions): NullableCheckoutSignal { +function createCheckoutInstance(clerk: Clerk, options: __experimental_CheckoutOptions): NullableCheckoutSignal { @@ - const checkoutKey = cacheKey({ + const checkoutKey = cacheKey({ userId: clerk.user.id, orgId: forOrganization === 'organization' ? clerk.organization?.id : undefined, planId, planPeriod, }); - if (cache.has(checkoutKey)) { - return ( - cache.get(checkoutKey) as { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> } - ).signals.computedSignal(); - } + const byInstance = getCache(clerk); + if (byInstance.has(checkoutKey)) { + return (byInstance.get(checkoutKey) as { signals: ReturnType<typeof createSignals> }).signals.computedSignal(); + } const signals = createSignals(); @@ - cache.set(checkoutKey, { resource: checkout, signals }); + byInstance.set(checkoutKey, { resource: checkout, signals }); return signals.computedSignal(); }
♻️ Duplicate comments (10)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)
350-353
: Initialize from known list; drop@ts-expect-error
.Resolve the incoming
paymentSource
by id frompaymentSources
.- const [selectedPaymentSource, setSelectedPaymentSource] = useState<CommercePaymentSourceResource | undefined>( - // @ts-expect-error - paymentSource is missing functions - paymentSource || paymentSources.find(p => p.isDefault), - ); + const [selectedPaymentSource, setSelectedPaymentSource] = useState<CommercePaymentSourceResource | undefined>(() => { + const byId = paymentSource ? paymentSources.find(p => p.id === paymentSource.id) : undefined; + return byId || paymentSources.find(p => p.isDefault); + });
29-33
: Guardtotals
to prevent null-deref.
totals
may be null; subsequent reads (Lines 35-113) will throw.- const { plan, totals, isImmediatePlanChange, planPeriod, freeTrialEndsAt } = checkout; - - if (!plan) { + const { plan, totals, isImmediatePlanChange, planPeriod, freeTrialEndsAt } = checkout; + + if (!plan || !totals) { return null; }packages/shared/src/react/hooks/useCheckout.ts (1)
31-39
: Includeclerk
in deps to avoid stale closure.Ensures the signal re-binds if the Clerk instance swaps.
- }, [ - // user?.id, organization?.id, - planId, - planPeriod, - forOrganization, - ]); + }, [planId, planPeriod, forOrganization, clerk]);packages/clerk-js/src/core/modules/checkout/instance.ts (1)
17-18
: Module-level cache leaks across users/orgs; scope to Clerk instance.Use a WeakMap keyed by
Clerk
and clear on sign-out/org switch.-const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>(); +type CacheEntry = { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }; +const cache = new WeakMap<Clerk, Map<CheckoutKey, CacheEntry>>(); + +function getCache(clerk: Clerk) { + let m = cache.get(clerk); + if (!m) { + m = new Map<CheckoutKey, CacheEntry>(); + cache.set(clerk, m); + } + return m; +}packages/types/src/clerk.ts (2)
73-90
: Fix JSDoc: replace SignIn with Checkout throughout.Docs still reference SignIn; should say Checkout to match the new API.
-/** - * The value returned by the `useSignInSignal` hook. - */ +/** + * The value returned by the Checkout signal (`__experimental_checkout`). + */ export interface CheckoutSignalValue { /** - * Represents the errors that occurred during the last fetch of the parent resource. + * Represents the errors that occurred during the last fetch of the Checkout resource. */ errors: GlobalErrors; /** - * The fetch status of the underlying `SignIn` resource. + * The fetch status of the underlying Checkout resource. */ fetchStatus: 'idle' | 'fetching'; /** - * An instance representing the currently active `SignIn`, with new APIs designed specifically for custom flows. + * An instance representing the currently active Checkout, with APIs designed for custom flows. */ checkout: CheckoutFutureResource; }
105-105
: Align __experimental_CheckoutFunction to return the signal function (CheckoutSignal)Return the signal function, not its value.
- Change type in packages/types/src/clerk.ts:
-type __experimental_CheckoutFunction = (options: __experimental_CheckoutOptions) => NullableCheckoutSignal; +type __experimental_CheckoutFunction = (options: __experimental_CheckoutOptions) => CheckoutSignal;
- Update runtime signature in packages/clerk-js/src/core/clerk.ts (currently
__experimental_checkout(...): NullableCheckoutSignal
) to returnCheckoutSignal
.- Verify and adjust call sites that expect the value vs the signal function (observed in packages/shared/src/react/hooks/useCheckout.ts and packages/react/src/isomorphicClerk.ts). Run: rg -n --hidden '__experimental_checkout' -g '!/node_modules/' -C2
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
3-14
: Remove ts-expect-error; import and type the computed signal correctly.Import CheckoutSignal/NullableCheckoutSignal and type computed; also use property shorthand.
import type { CheckoutFutureResourceLax, CheckoutSignalValue, + CheckoutSignal, + NullableCheckoutSignal, CommerceCheckoutJSON, CommerceCheckoutResource, CommerceCheckoutTotals, CommercePayerResource, CommerceSubscriptionPlanPeriod, ConfirmCheckoutParams, CreateCheckoutParams, } from '@clerk/types'; @@ - // @ts-expect-error - CheckoutSignal is not yet defined - const computedSignal: CheckoutSignal = computed(() => { + const computedSignal: CheckoutSignal = computed<NullableCheckoutSignal>(() => { const resource = resourceSignal().resource; const error = errorSignal().error; const fetchStatus = fetchSignal().status; const errors = errorsToParsedErrors(error); - return { errors: errors, fetchStatus, checkout: resource }; + return { errors, fetchStatus, checkout: resource }; });Also applies to: 127-139
195-207
: Guard against missing billing client or falsy checkout in start().Avoid assigning undefined to this.resource; return a clear error.
async start(): Promise<{ error: unknown }> { return this.runAsyncResourceTask( 'start', async () => { - const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout; - this.resource = checkout; + const billing = CommerceCheckout.clerk.billing; + if (!billing?.startCheckout) { + throw new Error('Checkout cannot start: billing client is not available.'); + } + const checkout = await billing.startCheckout(this.config); + if (!checkout) { + throw new Error('Checkout could not be initialized.'); + } + this.resource = checkout as CommerceCheckout; }, () => { this.resource = new CommerceCheckout(null); this.signals.resourceSignal({ resource: this }); }, ); }
209-213
: Prevent confirm() before initialization.Add precondition so we don’t hit an invalid path with undefined id.
async confirm(params: ConfirmCheckoutParams): Promise<{ error: unknown }> { - return this.runAsyncResourceTask('confirm', async () => { + return this.runAsyncResourceTask('confirm', async () => { + if (!this.resource?.id) { + throw new Error('Cannot confirm checkout before initialization.'); + } await this.resource.confirm(params); }); }
220-258
: Fix unbalanced batching in async task runner.Remove extra startBatch/endBatch; end once in finally.
return async (operationType, task, beforeTask?: () => void) => { @@ - const operationPromise = (async () => { - startBatch(); + const operationPromise = (async () => { + startBatch(); signals.errorSignal({ error: null }); signals.fetchSignal({ status: 'fetching' }); // signals.resourceSignal({ resource: null }); beforeTask?.(); - endBatch(); - startBatch(); + endBatch(); try { await task(); signals.resourceSignal({ resource: resource }); return { error: null }; } catch (err) { signals.errorSignal({ error: err }); - endBatch(); return { error: err }; } finally { pendingOperations.delete(operationType); - signals.fetchSignal({ status: 'idle' }); - endBatch(); + startBatch(); + signals.fetchSignal({ status: 'idle' }); + endBatch(); } })();
🧹 Nitpick comments (12)
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (4)
21-22
: Consider adding a fallback for server-side rendering.The media query check could potentially cause hydration mismatches if the server and client have different hover capabilities.
Consider using a more robust SSR-safe approach:
- const canHover = - typeof window === 'undefined' ? true : window.matchMedia('(hover: hover) and (pointer: fine)').matches; + const [canHover, setCanHover] = useState(false); + + useEffect(() => { + setCanHover(window.matchMedia('(hover: hover) and (pointer: fine)').matches); + }, []);
54-54
: Remove TypeScript ignore comment.The
@ts-ignore
comment should be replaced with proper typing or a more specific suppression.- // @ts-ignore - viewBox is a valid prop for svg viewBox='0 0 512 512'
Consider using a type assertion or extending the Box component's prop types to properly support SVG attributes when used with
as='svg'
.
46-48
: Verify unique ID generation performance.Multiple
useId()
calls within the same component could be consolidated or optimized.Consider generating a single base ID and deriving the others:
- const maskId1 = useId(); - const maskId2 = useId(); - const maskId3 = useId(); + const baseId = useId(); + const maskId1 = `${baseId}-mask1`; + const maskId2 = `${baseId}-mask2`; + const maskId3 = `${baseId}-mask3`;
82-116
: Consider extracting mask configuration to improve maintainability.The hardcoded mask configuration could be extracted to a constant for better maintainability and clarity.
const MASK_CONFIGS = [ { r: 225, maskStart: 10, maskEnd: 90 }, { r: 162.5, maskStart: 15, maskEnd: 85 }, { r: 100, maskStart: 20, maskEnd: 80 }, ] as const;Then reference it in both the gradient generation (lines 82-116) and mask generation (lines 118-133).
packages/react/src/isomorphicClerk.ts (1)
737-741
: Add explicit return type for public API.Type the field to satisfy “explicit return types” guideline and avoid inference drift.
- __experimental_checkout = (...args: Parameters<Clerk['__experimental_checkout']>) => { + __experimental_checkout: ( + ...args: Parameters<Clerk['__experimental_checkout']> + ) => ReturnType<Clerk['__experimental_checkout']> = (...args) => { return this.loaded && this.clerkjs ? this.clerkjs.__experimental_checkout(...args) : this.#stateProxy.checkoutSignal(...args); };packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)
143-145
: Clarify error message for wrong status.Make it actionable; this isn’t “not found”.
- if (checkout.status !== 'needs_confirmation') { - throw new Error('Checkout not found'); - } + if (checkout.status !== 'needs_confirmation') { + throw new Error('Clerk: Checkout is not ready to confirm.'); + }
151-158
: Remove@ts-expect-error
; handleunknown
cleanly and ensure cleanup.
sdk
returns{ error: unknown }
.handleError
accepts unknown. Also wrap in try/finally to always clear loading.- const { error } = await checkout.confirm(params); - - if (error) { - // @ts-expect-error - error is not an Error - handleError(error, [], card.setError); - } else { - onSubscriptionComplete?.(); - } - card.setIdle(); + try { + const { error } = await checkout.confirm(params); + if (error) { + handleError(error, [], card.setError); + } else { + onSubscriptionComplete?.(); + } + } finally { + card.setIdle(); + }packages/shared/src/react/hooks/useCheckout.ts (1)
40-52
: Stabilize subscription deps.Depending on deep properties can be brittle; depend on
clerk
object instead.- [signal, clerk.loaded, clerk.__internal_state], + [signal, clerk],packages/clerk-js/src/core/modules/checkout/instance.ts (1)
12-15
: TightenplanPeriod
type.Use
CommerceSubscriptionPlanPeriod
instead ofstring
.-function cacheKey(options: { userId: string; orgId?: string; planId: string; planPeriod: string }): CheckoutKey { +function cacheKey(options: { + userId: string; + orgId?: string; + planId: string; + planPeriod: import('@clerk/types').CommerceSubscriptionPlanPeriod; +}): CheckoutKey {packages/types/src/clerk.ts (1)
62-71
: Consider exporting GlobalErrors for consumers.Public types reference GlobalErrors; exporting helps downstream typing.
-interface GlobalErrors { +export interface GlobalErrors {packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
60-76
: Correct retry comment to match config.Current settings cap delays at ~2s; comment says 2s/4s/6s/8s.
- // Retry confirmation in case of a 500 error - // This will retry up to 3 times with an increasing delay - // It retries at 2s, 4s, 6s and 8s + // Retry confirmation on transient errors. + // Up to 4 attempts with ~2s delays (bounded by maxDelayBetweenRetries).
148-149
: Tighten pendingOperations type.Map never stores null; drop union.
- private readonly pendingOperations = new Map<string, Promise<{ error: unknown }> | null>(); + private readonly pendingOperations = new Map<string, Promise<{ error: unknown }>>();
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (14)
packages/clerk-js/src/core/clerk.ts
(2 hunks)packages/clerk-js/src/core/modules/checkout/__tests__/manager.spec.ts
(0 hunks)packages/clerk-js/src/core/modules/checkout/instance.ts
(3 hunks)packages/clerk-js/src/core/modules/checkout/manager.ts
(0 hunks)packages/clerk-js/src/core/resources/CommerceCheckout.ts
(3 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
(2 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
(4 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
(3 hunks)packages/clerk-js/src/ui/components/Checkout/parts.tsx
(3 hunks)packages/react/src/isomorphicClerk.ts
(1 hunks)packages/react/src/stateProxy.ts
(5 hunks)packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
(1 hunks)packages/shared/src/react/hooks/useCheckout.ts
(1 hunks)packages/types/src/clerk.ts
(2 hunks)
💤 Files with no reviewable changes (2)
- packages/clerk-js/src/core/modules/checkout/tests/manager.spec.ts
- packages/clerk-js/src/core/modules/checkout/manager.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/react/src/stateProxy.ts
- packages/clerk-js/src/ui/components/Checkout/parts.tsx
- packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
- packages/clerk-js/src/core/clerk.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{js,jsx,ts,tsx}
: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels
Files:
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/react/src/isomorphicClerk.ts
packages/types/src/clerk.ts
packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/react/src/isomorphicClerk.ts
packages/types/src/clerk.ts
packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/react/src/isomorphicClerk.ts
packages/types/src/clerk.ts
packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Packages should export TypeScript types alongside runtime code
Files:
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/react/src/isomorphicClerk.ts
packages/types/src/clerk.ts
packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
**/*.{ts,tsx}
: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidany
type - preferunknown
when type is uncertain, then narrow with type guards
Useinterface
for object shapes that might be extended
Usetype
for unions, primitives, and computed types
Preferreadonly
properties for immutable data structures
Useprivate
for internal implementation details
Useprotected
for inheritance hierarchies
Usepublic
explicitly for clarity in public APIs
Preferreadonly
for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Useconst assertions
for literal types:as const
Usesatisfies
operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports:import type { ... } from ...
Noany
types without justification
Proper error handling with typed errors
Consistent use ofreadonly
for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)
Files:
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/react/src/isomorphicClerk.ts
packages/types/src/clerk.ts
packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.{js,ts,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.
Files:
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/react/src/isomorphicClerk.ts
packages/types/src/clerk.ts
packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Unit tests should use Jest or Vitest as the test runner.
Files:
packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
**/__tests__/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/__tests__/**/*.{ts,tsx}
: Create type-safe test builders/factories
Use branded types for test isolation
Implement proper mock types that match interfaces
Files:
packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
packages/clerk-js/src/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/clerk-js-ui.mdc)
packages/clerk-js/src/ui/**/*.{ts,tsx}
: Element descriptors should always be camelCase
Use element descriptors in UI components to enable consistent theming and styling via appearance.elements
Element descriptors should generate unique, stable CSS classes for theming
Element descriptors should handle state classes (e.g., cl-loading, cl-active, cl-error, cl-open) automatically based on component state
Do not render hard-coded values; all user-facing strings must be localized using provided localization methods
Use the useLocalizations hook and localizationKeys utility for all text and error messages
Use the styled system (sx prop, theme tokens, responsive values) for custom component styling
Use useCardState for card-level state, useFormState for form-level state, and useLoadingStatus for loading states
Always use handleError utility for API errors and use translateError for localized error messages
Use useFormControl for form field state, implement proper validation, and handle loading and error states in forms
Use localization keys for all form labels and placeholders
Use element descriptors for consistent styling and follow the theme token system
Use the Card and FormContainer patterns for consistent UI structure
Files:
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{jsx,tsx}
: Use error boundaries in React components
Minimize re-renders in React components
**/*.{jsx,tsx}
: Always use functional components with hooks instead of class components
Follow PascalCase naming for components:UserProfile
,NavigationMenu
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Use useState for simple state management
Use useReducer for complex state logic
Implement proper state initialization
Use proper state updates with callbacks
Implement proper state cleanup
Use Context API for theme/authentication
Implement proper state selectors
Use proper state normalization
Implement proper state persistence
Use React.memo for expensive components
Implement proper useCallback for handlers
Use proper useMemo for expensive computations
Implement proper virtualization for lists
Use proper code splitting with React.lazy
Implement proper cleanup in useEffect
Use proper refs for DOM access
Implement proper event listener cleanup
Use proper abort controllers for fetch
Implement proper subscription cleanup
Use proper HTML elements
Implement proper ARIA attributes
Use proper heading hierarchy
Implement proper form labels
Use proper button types
Implement proper focus management
Use proper keyboard shortcuts
Implement proper tab order
Use proper skip links
Implement proper focus traps
Implement proper error boundaries
Use proper error logging
Implement proper error recovery
Use proper error messages
Implement proper error fallbacks
Use proper form validation
Implement proper error states
Use proper error messages
Implement proper form submission
Use proper form reset
Use proper component naming
Implement proper file naming
Use proper prop naming
Implement proper...
Files:
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/react.mdc)
**/*.tsx
: Use proper type definitions for props and state
Leverage TypeScript's type inference where possible
Use proper event types for handlers
Implement proper generic types for reusable components
Use proper type guards for conditional rendering
Files:
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
🧬 Code graph analysis (6)
packages/clerk-js/src/core/modules/checkout/instance.ts (2)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
CheckoutFuture
(144-218)createSignals
(127-142)packages/types/src/clerk.ts (2)
__experimental_CheckoutOptions
(99-103)NullableCheckoutSignal
(91-93)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
FieldError
(7-20)packages/types/src/commerce.ts (3)
CheckoutFutureResource
(1412-1427)ForPayerType
(122-122)CommerceSubscriptionPlanPeriod
(139-139)
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (2)
packages/clerk-js/src/ui/customizables/index.ts (1)
Box
(15-15)packages/clerk-js/src/ui/customizables/elementDescriptors.ts (1)
descriptors
(568-568)
packages/shared/src/react/hooks/useCheckout.ts (2)
packages/types/src/clerk.ts (1)
CheckoutSignalValue
(76-89)packages/clerk-js/src/ui/contexts/components/Checkout.ts (1)
useCheckoutContext
(10-37)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
packages/types/src/json.ts (1)
CommerceCheckoutJSON
(880-895)packages/types/src/clerk.ts (2)
CheckoutSignalValue
(76-89)CheckoutSignal
(95-97)packages/types/src/commerce.ts (3)
CheckoutFutureResourceLax
(1429-1446)CreateCheckoutParams
(1136-1155)ConfirmCheckoutParams
(1162-1188)packages/react/src/stateProxy.ts (1)
checkout
(188-194)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (6)
packages/react/src/stateProxy.ts (1)
checkout
(188-194)packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
plan
(168-170)totals
(174-176)packages/types/src/commerce.ts (1)
ConfirmCheckoutParams
(1162-1188)packages/clerk-js/src/ui/utils/errorHandler.ts (1)
handleError
(69-91)packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
usePaymentMethods
(32-40)packages/shared/src/react/hooks/usePaymentMethods.tsx (1)
usePaymentMethods
(9-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Formatting | Dedupe | Changeset
- GitHub Check: Build Packages
- GitHub Check: semgrep/ci
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (4)
packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx (2)
17-157
: LGTM! Clean component extraction with proper encapsulation.The
SuccessRing
component is well-implemented with proper state management, unique ID generation for SVG elements, and clean separation of concerns. The animation logic is correctly encapsulated within the component.
252-255
: LGTM! Clean component integration.The integration of
SuccessRing
maintains the existing API while improving code organization and reusability.packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)
197-199
: LGTM: totals readiness guard.This prevents early render crashes.
packages/shared/src/react/hooks/useCheckout.ts (1)
57-60
: Verify non-nullcheckout
invariant.Hook type says
CheckoutSignalValue
(non-nullcheckout
), but upstream can be nullable early. Confirm the invariant holds or relax the return type toNullableCheckoutSignal
.Would you like me to scan the codebase to ensure
__experimental_checkout
never yields a nullcheckout
at the timeuseCheckout
reads it?
type UseCheckoutReturn = ReturnType<typeof useCheckout>; | ||
|
||
describe('useCheckout type tests', () => { | ||
describe.skip('useCheckout type tests', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Don’t skip; update tests to new signal API.
The suite still references removed types (__experimental_CheckoutInstance
, __experimental_CheckoutCacheState
) and wrong unions (fetchStatus
includes 'error'). Align with CheckoutSignalValue
/CheckoutFutureResource
and unskip.
-import type {
- ClerkAPIResponseError,
- CommerceCheckoutResource,
- CommerceSubscriptionPlanPeriod,
- ConfirmCheckoutParams,
- SetActiveNavigate,
-} from '@clerk/types';
+import type {
+ ClerkAPIResponseError,
+ CommerceCheckoutResource,
+ CommerceSubscriptionPlanPeriod,
+ ConfirmCheckoutParams,
+ SetActiveNavigate,
+ CheckoutSignalValue,
+ CheckoutFutureResource,
+} from '@clerk/types';
@@
-describe.skip('useCheckout type tests', () => {
+describe('useCheckout type tests', () => {
@@
- type CheckoutObject = UseCheckoutReturn['checkout'];
+ type CheckoutObject = CheckoutSignalValue['checkout'];
@@
- type Methods = Pick<CheckoutObject, 'confirm' | 'start' | 'clear' | 'finalize' | 'getState'>;
+ type Methods = Pick<CheckoutObject, 'confirm' | 'start'>;
@@
- expectTypeOf<MethodNames>().toEqualTypeOf<'confirm' | 'start' | 'clear' | 'finalize' | 'getState'>();
+ expectTypeOf<MethodNames>().toEqualTypeOf<'confirm' | 'start'>();
@@
- type Methods = Pick<CheckoutObject, 'confirm' | 'start' | 'clear' | 'finalize' | 'getState'>;
+ type Methods = Pick<CheckoutObject, 'confirm' | 'start'>;
@@
- type ClearMethod = Methods['clear'];
- type FinalizeMethod = Methods['finalize'];
- type GetStateMethod = Methods['getState'];
+ // removed in signal-based API
@@
- expectTypeOf<ClearMethod>().returns.toBeVoid();
- expectTypeOf<FinalizeMethod>().parameter(0).toEqualTypeOf<{ navigate?: SetActiveNavigate } | undefined>();
- expectTypeOf<GetStateMethod>().returns.toEqualTypeOf<__experimental_CheckoutCacheState>();
+ // n/a in v2
@@
- type StatusProps = Pick<CheckoutObject, 'isStarting' | 'isConfirming' | 'error' | 'status' | 'fetchStatus'>;
+ type StatusProps = Pick<CheckoutObject, 'status'> & { fetchStatus: CheckoutSignalValue['fetchStatus'] };
@@
- expectTypeOf<FetchStatus>().toEqualTypeOf<'idle' | 'fetching' | 'error'>();
+ expectTypeOf<FetchStatus>().toEqualTypeOf<'idle' | 'fetching'>();
@@
- expectTypeOf<CheckoutObject['confirm']>().toEqualTypeOf<__experimental_CheckoutInstance['confirm']>();
- expectTypeOf<CheckoutObject['start']>().toEqualTypeOf<__experimental_CheckoutInstance['start']>();
- expectTypeOf<CheckoutObject['clear']>().toEqualTypeOf<() => void>();
- expectTypeOf<CheckoutObject['finalize']>().toEqualTypeOf<
- (params?: { navigate?: SetActiveNavigate }) => void
- >();
- expectTypeOf<CheckoutObject['getState']>().toEqualTypeOf<() => __experimental_CheckoutCacheState>();
+ expectTypeOf<CheckoutObject['confirm']>().parameter(0).toEqualTypeOf<ConfirmCheckoutParams>();
+ expectTypeOf<CheckoutObject['start']>().parameters.toEqualTypeOf<[]>();
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
describe.skip('useCheckout type tests', () => { | |
describe('useCheckout type tests', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)
148-166
: Make confirm flow exception-safe; always clear loading.A thrown error (unknown from confirm/handleError) leaves the card in loading state and skips error handling. Use try/catch/finally and improve the thrown message.
- if (checkout.status !== 'needs_confirmation') { - throw new Error('Checkout not found'); - } + if (checkout.status !== 'needs_confirmation') { + throw new Error(`Checkout not ready (status: ${checkout.status})`); + } const confirmCheckout = async (params: ConfirmCheckoutParams) => { - card.setLoading(); - card.setError(undefined); - - const { error } = await checkout.confirm(params); - - if (error) { - // @ts-expect-error - error is not an Error - handleError(error, [], card.setError); - } else { - onSubscriptionComplete?.(); - } - card.setIdle(); + card.setLoading(); + card.setError(undefined); + try { + const { error } = await checkout.confirm(params); + if (error) { + handleError(error as unknown, [], card.setError); + return; + } + onSubscriptionComplete?.(); + } catch (err) { + handleError(err, [], card.setError); + } finally { + card.setIdle(); + } };
215-219
: Localize the aria-label.Hard-coded string; use
useLocalizations
per guidelines.+ const { t } = useLocalizations(); ... - <SegmentedControl.Root - aria-label='Payment method source' + <SegmentedControl.Root + aria-label={t(localizationKeys('commerce.checkout.paymentMethodSource'))}packages/react/src/stateProxy.ts (1)
198-206
: HardengateProperty
for null targets.Prevents crashes when the signal’s
checkout
is stillnull
even after Clerk is loaded.- private gateProperty<T extends object, K extends keyof T>(getTarget: () => T, key: K, defaultValue: T[K]) { - return (() => { - if (!inBrowser() || !this.isomorphicClerk.loaded) { - return defaultValue; - } - const t = getTarget(); - return t[key]; - })(); - } + private gateProperty<T extends object, K extends keyof T>( + getTarget: () => T | null, + key: K, + defaultValue: T[K], + ) { + return (() => { + if (!inBrowser() || !this.isomorphicClerk.loaded) { + return defaultValue; + } + const t = getTarget() as any; + const v = t?.[key]; + return typeof v === 'undefined' ? defaultValue : (v as T[K]); + })(); + }
♻️ Duplicate comments (4)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)
34-41
: Guardtotals
to avoid null deref (repeat).
totals
is used below without a null check; add it to the early return guard.- if (!plan) { + if (!plan || !totals) { return null; }packages/types/src/clerk.ts (1)
73-89
: Fix copy/paste JSDoc: replace SignIn references with Checkout (repeat).Docstrings still reference SignIn; update to Checkout to match the API.
-/** - * The value returned by the `useSignInSignal` hook. - */ +/** + * The value returned by the Checkout signal. + */ export interface CheckoutSignalValue { /** - * Represents the errors that occurred during the last fetch of the parent resource. + * Represents the errors that occurred during the last fetch of the Checkout resource. */ errors: GlobalErrors; /** - * The fetch status of the underlying `SignIn` resource. + * The fetch status of the underlying Checkout resource. */ fetchStatus: 'idle' | 'fetching'; /** - * An instance representing the currently active `SignIn`, with new APIs designed specifically for custom flows. + * An instance representing the currently active Checkout, with APIs designed for custom flows. */ checkout: CheckoutFutureResource; }packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
128-142
: Type the computed signal explicitly and drop redundant property shorthandAdd the proper generic to
computed
and use property shorthand. This also mirrors previous feedback.-import type { +import type { CheckoutFutureResourceLax, CheckoutSignal, CheckoutSignalValue, + NullableCheckoutSignalValue, @@ -export const createSignals = () => { +export const createSignals = () => { @@ - const computedSignal: CheckoutSignal = computed(() => { + const computedSignal: CheckoutSignal = computed<NullableCheckoutSignalValue>(() => { @@ - const errors = errorsToParsedErrors(error); - return { errors: errors, fetchStatus, checkout: resource }; + const errors = errorsToParsedErrors(error); + return { errors, fetchStatus, checkout: resource }; });
195-207
: Guard against missing billing client and falsy checkout (can setthis.resource
to undefined)If
clerk.billing
is absent or returns a falsy value,this.resource
becomes invalid. Add a precondition and validate the result.async start(): Promise<{ error: unknown }> { return this.runAsyncResourceTask( 'start', async () => { - const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout; - this.resource = checkout; + const billing = CommerceCheckout.clerk.billing; + if (!billing?.startCheckout) { + throw new Error('Checkout cannot start: billing client is not available.'); + } + const checkout = await billing.startCheckout(this.config); + if (!checkout) { + throw new Error('Checkout could not be initialized.'); + } + this.resource = checkout as CommerceCheckout; }, () => { this.resource = new CommerceCheckout(null); this.signals.resourceSignal({ resource: this }); }, ); }
🧹 Nitpick comments (9)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)
159-161
: Remove@ts-expect-error
; cast to unknown forhandleError
.Avoid suppression;
handleError
is designed for unknown errors.- // @ts-expect-error - error is not an Error - handleError(error, [], card.setError); + handleError(error as unknown, [], card.setError);
396-401
: Avoid controlled/uncontrolled warnings on hidden input.Ensure the value is always a string.
- value={selectedPaymentSource?.id} + value={selectedPaymentSource?.id ?? ''}And similarly for the hidden input at Lines 418–423.
Also applies to: 418-423
packages/react/src/stateProxy.ts (1)
208-220
: Gracefully guardgateMethod
against null targets.Avoids
.apply
on undefined and surfaces a clearer error.- const t = getTarget(); - return (t[key] as (...args: Parameters<F>) => ReturnType<F>).apply(t, args); + const t = getTarget() as any; + if (!t || typeof t[key] !== 'function') { + return errorThrower.throw(`Attempted to call (${key}) before checkout is ready.`); + } + return (t[key] as (...args: Parameters<F>) => ReturnType<F>).apply(t, args);packages/types/src/commerce.ts (1)
1356-1446
: Type surface for CheckoutFuture looks solid; minor nit on planPeriodStart docs.
planPeriodStart
isnumber | undefined
in confirmed states andnull
in initialization viaForceNull
. Consider noting “number (ms) | undefined | null” in the JSDoc for clarity.packages/clerk-js/src/core/resources/CommerceCheckout.ts (5)
61-92
: Add JSDoc to public methodconfirm
Public APIs need JSDoc per guidelines. Briefly document behavior and retry conditions.
+ /** + * Confirms the checkout. Retries on 5xx and `checkout_already_in_progress` (409) with bounded backoff. + * @param params Checkout confirmation parameters + * @returns The updated checkout instance + */ confirm = (params: ConfirmCheckoutParams): Promise<this> => {
95-126
: Tighten error parsing; avoid shadowing and simplify pushesAvoid parameter shadowing and use
??=
to trim branches. Ifglobal
is intended for developer-facing messages, consider mapping toe.message
.-function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] { +function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] { @@ - if (!isClerkAPIResponseError(error)) { + if (!isClerkAPIResponseError(error)) { parsedErrors.raw = []; parsedErrors.global = []; return parsedErrors; } - error.errors.forEach(error => { - if (parsedErrors.raw) { - parsedErrors.raw.push(error); - } else { - parsedErrors.raw = [error]; - } - - if (parsedErrors.global) { - parsedErrors.global.push(error); - } else { - parsedErrors.global = [error]; - } - }); + for (const e of error.errors) { + parsedErrors.raw ??= []; + parsedErrors.raw.push(e); + parsedErrors.global ??= []; + parsedErrors.global.push(e); + // If GlobalErrors.global should be strings, use: + // parsedErrors.global.push(e.long_message ?? e.message ?? e.code); + } return parsedErrors; }
128-142
: Add JSDoc tocreateSignals
(public API)Expose intent and shapes for consumers.
+/** + * Creates reactive signals for the checkout flow. + * - resourceSignal: holds the current CheckoutFuture resource + * - errorSignal: holds the last operation error + * - fetchSignal: indicates 'idle' | 'fetching' + * - computedSignal: CheckoutSignal combining the above for consumers + */ export const createSignals = () => {
144-154
: Add JSDoc toCheckoutFuture
(public API)Per guidelines, document lifecycle and concurrency behavior.
+/** + * Public facade for the signal-driven checkout lifecycle. + * Ensures per-operation concurrency control and emits batched updates to signals. + */ export class CheckoutFuture implements CheckoutFutureResourceLax {
61-91
: Align retry comment and config in CommerceCheckout.confirm
- The comment's delay sequence is wrong: with initialDelay=2000, factor=1.1 and maxDelayBetweenRetries=2000 the retries will be ~2s (capped), not 2s/4s/6s/8s.
- The "retry up to 3 times" text is consistent with the shouldRetry check (iterations >= 4 allows 3 retries after the initial attempt).
Suggested minimal change (update comment to match actual behavior):
- // Retry confirmation in case of a 500 error - // This will retry up to 3 times with an increasing delay - // It retries at 2s, 4s, 6s and 8s + // Retry confirmation in case of a 500 error + // Attempts up to 4 times total (initial + up to 3 retries). Delays start at 2s + // and are capped at 2s by maxDelayBetweenRetries (so retries will be ~2s apart).Optional alternative if you want growing delays: set maxDelayBetweenRetries: 8 * 1000 and choose an appropriate factor (e.g., factor: 2 for 2s→4s→8s). Linear increments of 2s (2s,4s,6s,8s) are not supported by retry() without changing its implementation.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
packages/clerk-js/src/core/resources/CommerceCheckout.ts
(3 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
(5 hunks)packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
(1 hunks)packages/react/src/stateProxy.ts
(5 hunks)packages/types/src/clerk.ts
(2 hunks)packages/types/src/commerce.ts
(2 hunks)packages/types/src/utils.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/types/src/utils.ts
🧰 Additional context used
📓 Path-based instructions (9)
packages/clerk-js/src/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/clerk-js-ui.mdc)
packages/clerk-js/src/ui/**/*.{ts,tsx}
: Element descriptors should always be camelCase
Use element descriptors in UI components to enable consistent theming and styling via appearance.elements
Element descriptors should generate unique, stable CSS classes for theming
Element descriptors should handle state classes (e.g., cl-loading, cl-active, cl-error, cl-open) automatically based on component state
Do not render hard-coded values; all user-facing strings must be localized using provided localization methods
Use the useLocalizations hook and localizationKeys utility for all text and error messages
Use the styled system (sx prop, theme tokens, responsive values) for custom component styling
Use useCardState for card-level state, useFormState for form-level state, and useLoadingStatus for loading states
Always use handleError utility for API errors and use translateError for localized error messages
Use useFormControl for form field state, implement proper validation, and handle loading and error states in forms
Use localization keys for all form labels and placeholders
Use element descriptors for consistent styling and follow the theme token system
Use the Card and FormContainer patterns for consistent UI structure
Files:
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{js,jsx,ts,tsx}
: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels
Files:
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
packages/types/src/commerce.ts
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/react/src/stateProxy.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
packages/types/src/commerce.ts
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/react/src/stateProxy.ts
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
packages/types/src/commerce.ts
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/react/src/stateProxy.ts
packages/**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Packages should export TypeScript types alongside runtime code
Files:
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
packages/types/src/commerce.ts
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/react/src/stateProxy.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
**/*.{ts,tsx}
: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidany
type - preferunknown
when type is uncertain, then narrow with type guards
Useinterface
for object shapes that might be extended
Usetype
for unions, primitives, and computed types
Preferreadonly
properties for immutable data structures
Useprivate
for internal implementation details
Useprotected
for inheritance hierarchies
Usepublic
explicitly for clarity in public APIs
Preferreadonly
for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Useconst assertions
for literal types:as const
Usesatisfies
operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports:import type { ... } from ...
Noany
types without justification
Proper error handling with typed errors
Consistent use ofreadonly
for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)
Files:
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
packages/types/src/commerce.ts
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/react/src/stateProxy.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{jsx,tsx}
: Use error boundaries in React components
Minimize re-renders in React components
**/*.{jsx,tsx}
: Always use functional components with hooks instead of class components
Follow PascalCase naming for components:UserProfile
,NavigationMenu
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Use useState for simple state management
Use useReducer for complex state logic
Implement proper state initialization
Use proper state updates with callbacks
Implement proper state cleanup
Use Context API for theme/authentication
Implement proper state selectors
Use proper state normalization
Implement proper state persistence
Use React.memo for expensive components
Implement proper useCallback for handlers
Use proper useMemo for expensive computations
Implement proper virtualization for lists
Use proper code splitting with React.lazy
Implement proper cleanup in useEffect
Use proper refs for DOM access
Implement proper event listener cleanup
Use proper abort controllers for fetch
Implement proper subscription cleanup
Use proper HTML elements
Implement proper ARIA attributes
Use proper heading hierarchy
Implement proper form labels
Use proper button types
Implement proper focus management
Use proper keyboard shortcuts
Implement proper tab order
Use proper skip links
Implement proper focus traps
Implement proper error boundaries
Use proper error logging
Implement proper error recovery
Use proper error messages
Implement proper error fallbacks
Use proper form validation
Implement proper error states
Use proper error messages
Implement proper form submission
Use proper form reset
Use proper component naming
Implement proper file naming
Use proper prop naming
Implement proper...
Files:
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.{js,ts,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.
Files:
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
packages/types/src/commerce.ts
packages/types/src/clerk.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/react/src/stateProxy.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/react.mdc)
**/*.tsx
: Use proper type definitions for props and state
Leverage TypeScript's type inference where possible
Use proper event types for handlers
Implement proper generic types for reusable components
Use proper type guards for conditional rendering
Files:
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
🧬 Code graph analysis (6)
packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx (2)
packages/types/src/utils.ts (1)
RemoveFunctions
(127-129)packages/types/src/commerce.ts (1)
CommercePaymentSourceResource
(487-534)
packages/types/src/commerce.ts (1)
packages/types/src/utils.ts (3)
Prettify
(131-133)RemoveFunctions
(127-129)ForceNull
(47-49)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
FieldError
(7-20)packages/types/src/commerce.ts (3)
CheckoutFutureResource
(1412-1427)ForPayerType
(122-122)CommerceSubscriptionPlanPeriod
(139-139)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (8)
packages/react/src/stateProxy.ts (1)
checkout
(190-196)packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)
plan
(168-170)totals
(174-176)paymentSource
(187-189)packages/types/src/commerce.ts (2)
ConfirmCheckoutParams
(1162-1188)CommercePaymentSourceResource
(487-534)packages/clerk-js/src/ui/utils/errorHandler.ts (1)
handleError
(69-91)packages/shared/src/react/hooks/index.ts (1)
usePaymentMethods
(13-13)packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
usePaymentMethods
(32-40)packages/shared/src/react/hooks/usePaymentMethods.tsx (1)
usePaymentMethods
(9-21)packages/types/src/utils.ts (1)
RemoveFunctions
(127-129)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)
packages/types/src/json.ts (1)
CommerceCheckoutJSON
(880-895)packages/types/src/clerk.ts (2)
CheckoutSignalValue
(76-89)CheckoutSignal
(95-97)packages/types/src/commerce.ts (3)
CheckoutFutureResourceLax
(1429-1446)CreateCheckoutParams
(1136-1155)ConfirmCheckoutParams
(1162-1188)
packages/react/src/stateProxy.ts (3)
packages/types/src/commerce.ts (3)
ForPayerType
(122-122)CommerceSubscriptionPlanPeriod
(139-139)CheckoutFutureResource
(1412-1427)packages/types/src/clerk.ts (2)
CheckoutSignalValue
(76-89)Clerk
(169-892)packages/clerk-js/src/core/clerk.ts (1)
Clerk
(200-2948)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Formatting | Dedupe | Changeset
- GitHub Check: Build Packages
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (8)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (2)
373-384
: Verify intent:shouldDefaultBeUsed
condition appears inverted.If “default should be used,” why render the picker when true? Please confirm or rename to reflect behavior.
Would you like me to flip the condition and render the hidden input when
shouldDefaultBeUsed
is true?
355-358
: Type narrowing forselectedPaymentSource
looks good.Using
RemoveFunctions<CommercePaymentSourceResource>
avoids leaking methods into display-only components.packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx (1)
1-10
: Prop type change toRemoveFunctions
is appropriate.Aligns with display-only usage; no runtime changes.
packages/react/src/stateProxy.ts (1)
32-36
: New checkout signal surface: API shape looks consistent.Types and parameters align with the new
CheckoutFutureResource
.Also applies to: 51-53
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
36-39
: Constructor default to null looks goodThe nullable JSON input with a sane default enables a clean “uninitialized” state. No issues here.
209-216
: Good guard beforeconfirm
Prevents invalid API paths by enforcing initialization first. Looks correct.
223-259
: Balanced batching and per-operation deduping look solid
startBatch/endBatch
are balanced, andpendingOperations
ensures coalesced concurrent calls. Good structure.
156-193
: Getters return null pre-init (not undefined) — adjust types or add guardsTypes show CheckoutFutureResource uses ForceNull for status === 'needs_initialization', so these getters can return null (not undefined). Either annotate the getters with nullable return types (e.g. Foo | null) or throw/guard when status === 'needs_initialization' if non-null values are required.
Likely an incorrect or invalid review comment.
const { totals, freeTrialEndsAt } = checkout; | ||
const { data: paymentSources } = usePaymentMethods(); | ||
|
||
const [paymentMethodSource, setPaymentMethodSource] = useState<PaymentMethodSource>(() => | ||
paymentSources.length > 0 ? 'existing' : 'new', | ||
); | ||
|
||
if (!id) { | ||
if (!totals) { | ||
return null; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default paymentSources
to an empty array.
Prevents runtime errors when data
is initially undefined.
- const { data: paymentSources } = usePaymentMethods();
+ const { data: paymentSources = [] } = usePaymentMethods();
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const { totals, freeTrialEndsAt } = checkout; | |
const { data: paymentSources } = usePaymentMethods(); | |
const [paymentMethodSource, setPaymentMethodSource] = useState<PaymentMethodSource>(() => | |
paymentSources.length > 0 ? 'existing' : 'new', | |
); | |
if (!id) { | |
if (!totals) { | |
return null; | |
} | |
const { totals, freeTrialEndsAt } = checkout; | |
const { data: paymentSources = [] } = usePaymentMethods(); | |
const [paymentMethodSource, setPaymentMethodSource] = useState<PaymentMethodSource>(() => | |
paymentSources.length > 0 ? 'existing' : 'new', | |
); | |
if (!totals) { | |
return null; | |
} |
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx around lines
195 to 205, paymentSources can be undefined because usePaymentMethods().data may
be initially undefined; default paymentSources to an empty array when
destructuring so the subsequent state initializer (and any array operations)
don't throw. Update the destructuring to provide a default empty array for data
(paymentSources) and ensure the useState initializer continues to handle an
empty array correctly.
private buildCheckoutProxy(params: CheckoutSignalProps): CheckoutSignalValue { | ||
const gateProperty = this.gateProperty.bind(this); | ||
const targetCheckout = () => this.checkout(params); | ||
const target = () => targetCheckout().checkout as CheckoutFutureResource; | ||
|
||
return { | ||
errors: { | ||
raw: null, | ||
global: null, | ||
}, | ||
fetchStatus: 'idle' as const, | ||
checkout: { | ||
get status() { | ||
return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization'; | ||
}, | ||
get externalClientSecret() { | ||
return gateProperty(target, 'externalClientSecret', null) as null; | ||
}, | ||
get externalGatewayId() { | ||
return gateProperty(target, 'externalGatewayId', null) as null; | ||
}, | ||
get paymentSource() { | ||
return gateProperty(target, 'paymentSource', null) as null; | ||
}, | ||
get plan() { | ||
return gateProperty(target, 'plan', null) as null; | ||
}, | ||
get planPeriod() { | ||
return gateProperty(target, 'planPeriod', null) as null; | ||
}, | ||
get totals() { | ||
return gateProperty(target, 'totals', null) as null; | ||
}, | ||
get isImmediatePlanChange() { | ||
return gateProperty(target, 'isImmediatePlanChange', false) as null; | ||
}, | ||
get freeTrialEndsAt() { | ||
return gateProperty(target, 'freeTrialEndsAt', null) as null; | ||
}, | ||
get payer() { | ||
return gateProperty(target, 'payer', null) as null; | ||
}, | ||
get planPeriodStart() { | ||
return gateProperty(target, 'planPeriodStart', null) as null; | ||
}, | ||
|
||
start: this.gateMethod<ReturnType<typeof target>, 'start'>(target, 'start'), | ||
confirm: this.gateMethod<ReturnType<typeof target>, 'confirm'>(target, 'confirm'), | ||
}, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Fix nullability and default-value mismatches in checkout proxy.
targetCheckout().checkout
can benull
; current cast risks crashes.- Several getters return runtime booleans while typed as
null
viaas null
.
- const targetCheckout = () => this.checkout(params);
- const target = () => targetCheckout().checkout as CheckoutFutureResource;
+ const targetCheckout = () => this.checkout(params);
+ const target = () => targetCheckout().checkout;
...
- get status() {
- return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization';
+ get status() {
+ return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization';
},
get externalClientSecret() {
- return gateProperty(target, 'externalClientSecret', null) as null;
+ return gateProperty(target, 'externalClientSecret', null);
},
get externalGatewayId() {
- return gateProperty(target, 'externalGatewayId', null) as null;
+ return gateProperty(target, 'externalGatewayId', null);
},
get paymentSource() {
- return gateProperty(target, 'paymentSource', null) as null;
+ return gateProperty(target, 'paymentSource', null);
},
get plan() {
- return gateProperty(target, 'plan', null) as null;
+ return gateProperty(target, 'plan', null);
},
get planPeriod() {
- return gateProperty(target, 'planPeriod', null) as null;
+ return gateProperty(target, 'planPeriod', null);
},
get totals() {
- return gateProperty(target, 'totals', null) as null;
+ return gateProperty(target, 'totals', null);
},
get isImmediatePlanChange() {
- return gateProperty(target, 'isImmediatePlanChange', false) as null;
+ return gateProperty(target, 'isImmediatePlanChange', null);
},
get freeTrialEndsAt() {
- return gateProperty(target, 'freeTrialEndsAt', null) as null;
+ return gateProperty(target, 'freeTrialEndsAt', null);
},
get payer() {
- return gateProperty(target, 'payer', null) as null;
+ return gateProperty(target, 'payer', null);
},
get planPeriodStart() {
- return gateProperty(target, 'planPeriodStart', null) as null;
+ return gateProperty(target, 'planPeriodStart', null);
},
Additionally, update gateProperty
to handle null
targets and undefined values safely (see next comment).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
private buildCheckoutProxy(params: CheckoutSignalProps): CheckoutSignalValue { | |
const gateProperty = this.gateProperty.bind(this); | |
const targetCheckout = () => this.checkout(params); | |
const target = () => targetCheckout().checkout as CheckoutFutureResource; | |
return { | |
errors: { | |
raw: null, | |
global: null, | |
}, | |
fetchStatus: 'idle' as const, | |
checkout: { | |
get status() { | |
return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization'; | |
}, | |
get externalClientSecret() { | |
return gateProperty(target, 'externalClientSecret', null) as null; | |
}, | |
get externalGatewayId() { | |
return gateProperty(target, 'externalGatewayId', null) as null; | |
}, | |
get paymentSource() { | |
return gateProperty(target, 'paymentSource', null) as null; | |
}, | |
get plan() { | |
return gateProperty(target, 'plan', null) as null; | |
}, | |
get planPeriod() { | |
return gateProperty(target, 'planPeriod', null) as null; | |
}, | |
get totals() { | |
return gateProperty(target, 'totals', null) as null; | |
}, | |
get isImmediatePlanChange() { | |
return gateProperty(target, 'isImmediatePlanChange', false) as null; | |
}, | |
get freeTrialEndsAt() { | |
return gateProperty(target, 'freeTrialEndsAt', null) as null; | |
}, | |
get payer() { | |
return gateProperty(target, 'payer', null) as null; | |
}, | |
get planPeriodStart() { | |
return gateProperty(target, 'planPeriodStart', null) as null; | |
}, | |
start: this.gateMethod<ReturnType<typeof target>, 'start'>(target, 'start'), | |
confirm: this.gateMethod<ReturnType<typeof target>, 'confirm'>(target, 'confirm'), | |
}, | |
}; | |
} | |
private buildCheckoutProxy(params: CheckoutSignalProps): CheckoutSignalValue { | |
const gateProperty = this.gateProperty.bind(this); | |
const targetCheckout = () => this.checkout(params); | |
const target = () => targetCheckout().checkout; | |
return { | |
errors: { | |
raw: null, | |
global: null, | |
}, | |
fetchStatus: 'idle' as const, | |
checkout: { | |
get status() { | |
return gateProperty(target, 'status', 'needs_initialization') as 'needs_initialization'; | |
}, | |
get externalClientSecret() { | |
return gateProperty(target, 'externalClientSecret', null); | |
}, | |
get externalGatewayId() { | |
return gateProperty(target, 'externalGatewayId', null); | |
}, | |
get paymentSource() { | |
return gateProperty(target, 'paymentSource', null); | |
}, | |
get plan() { | |
return gateProperty(target, 'plan', null); | |
}, | |
get planPeriod() { | |
return gateProperty(target, 'planPeriod', null); | |
}, | |
get totals() { | |
return gateProperty(target, 'totals', null); | |
}, | |
get isImmediatePlanChange() { | |
return gateProperty(target, 'isImmediatePlanChange', null); | |
}, | |
get freeTrialEndsAt() { | |
return gateProperty(target, 'freeTrialEndsAt', null); | |
}, | |
get payer() { | |
return gateProperty(target, 'payer', null); | |
}, | |
get planPeriodStart() { | |
return gateProperty(target, 'planPeriodStart', null); | |
}, | |
start: this.gateMethod<ReturnType<typeof target>, 'start'>(target, 'start'), | |
confirm: this.gateMethod<ReturnType<typeof target>, 'confirm'>(target, 'confirm'), | |
}, | |
}; | |
} |
🤖 Prompt for AI Agents
In packages/react/src/stateProxy.ts around lines 123 to 173, the checkout proxy
assumes targetCheckout().checkout is always non-null and several getters use
incorrect null-typed defaults (e.g. booleans typed as null); update the proxy so
target is typed as CheckoutFutureResource | null (don’t force-cast), have
gateProperty calls use appropriate default values and types (use false for
boolean fields like isImmediatePlanChange, keep null only for genuinely nullable
objects like
externalClientSecret/externalGatewayId/paymentSource/plan/planPeriod/totals/freeTrialEndsAt/payer/planPeriodStart),
and replace the current target() => targetCheckout().checkout with a null-safe
accessor that returns null when checkout is absent; also update gateProperty (as
noted in the other comment) to gracefully handle a null target and undefined
property values so the getters never throw when checkout is null or properties
are undefined.
813edcc
to
6be9212
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
packages/clerk-js/src/core/modules/checkout/instance.ts (1)
17-18
: Cache survives user/org switches — scope per Clerk instance or add invalidationModule-level Map will leak stale entries across sign-out/sign-in and org changes. Move cache to a WeakMap keyed by Clerk or expose a clear function and call it on signOut and organization changes.
Apply:
-const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>(); +const instanceCache = new WeakMap< + Clerk, + Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }> +>(); + +function getCache(clerk: Clerk) { + let m = instanceCache.get(clerk); + if (!m) { + m = new Map(); + instanceCache.set(clerk, m); + } + return m; +} + +export function clearCheckoutCache(clerk: Clerk) { + instanceCache.get(clerk)?.clear(); +}And inside
createCheckoutInstance
useconst cache = getCache(clerk);
.packages/clerk-js/src/core/resources/CommerceCheckout.ts (1)
197-209
: Guard billing availability and falsy checkout to prevent undefined assignment
billing?.startCheckout
may be undefined and could yieldundefined
assigned tothis.resource
. Throw a clear, actionable error instead.- async () => { - const checkout = (await CommerceCheckout.clerk.billing?.startCheckout(this.config)) as CommerceCheckout; - this.resource = checkout; - }, + async () => { + const billing = CommerceCheckout.clerk.billing; + if (!billing?.startCheckout) { + throw new Error('Clerk: Billing client unavailable. Ensure billing is enabled and ClerkProvider is configured.'); + } + const checkout = await billing.startCheckout(this.config); + if (!checkout) { + throw new Error('Clerk: Failed to initialize checkout.'); + } + this.resource = checkout as CommerceCheckout; + },packages/types/src/clerk.ts (1)
73-89
: Fix copy/paste JSDoc: replace SignIn with CheckoutUpdate comments to reflect Checkout.
-/** - * The value returned by the `useSignInSignal` hook. - */ +/** + * The value returned by the Checkout signal. + */ @@ - /** - * Represents the errors that occurred during the last fetch of the parent resource. - */ + /** Represents the errors that occurred during the last fetch of the Checkout resource. */ @@ - /** - * The fetch status of the underlying `SignIn` resource. - */ + /** The fetch status of the underlying Checkout resource. */ @@ - /** - * An instance representing the currently active `SignIn`, with new APIs designed specifically for custom flows. - */ + /** An instance representing the currently active Checkout, with APIs designed for custom flows. */
🧹 Nitpick comments (8)
packages/types/src/utils.ts (2)
44-50
: Confirm intended nullability semantics for ForceNullCurrent mapping preserves optional and readonly modifiers. If you meant “every key present and set to null,” make properties required.
Example tweak:
-export type ForceNull<T> = { - [K in keyof T]: null; -}; +export type ForceNull<T> = { + [K in keyof T]-?: null; +};
124-134
: Tighten RemoveFunctions (avoid any; include constructors) and document Prettify
- Replace any with unknown to meet typing guidelines.
- Optionally exclude constructor properties as well.
- Add JSDoc for Prettify (public API).
-/** - * Utility type that removes function properties from a type. - */ -export type RemoveFunctions<T extends object> = { - [K in keyof T as T[K] extends (...args: any[]) => any ? never : K]: T[K]; -}; +/** + * Utility type that removes callable properties (methods) from a type. + * Note: shallow — does not traverse nested objects. + */ +export type RemoveFunctions<T extends object> = { + [K in keyof T as T[K] extends ((...args: unknown[]) => unknown) + | (abstract new (...args: unknown[]) => unknown) ? never : K]: T[K]; +}; -export type Prettify<T> = { - [K in keyof T]: T[K]; -} & {}; +/** + * Normalizes/expands mapped or intersection types for nicer editor display; no semantic change. + * @public + */ +export type Prettify<T> = { [K in keyof T]: T[K] } & {};If you also want a deep variant, I can add DeepRemoveFunctions separately.
packages/clerk-js/src/core/modules/checkout/instance.ts (2)
12-15
: Tighten types and avoid delimiter collisions in cache keyUse the domain type for
planPeriod
and a collision-safe key.-import type { __experimental_CheckoutOptions, CheckoutSignalValue } from '@clerk/types'; +import type { __experimental_CheckoutOptions, CheckoutSignalValue, CommerceSubscriptionPlanPeriod } from '@clerk/types'; @@ -function cacheKey(options: { userId: string; orgId?: string; planId: string; planPeriod: string }): CheckoutKey { - const { userId, orgId, planId, planPeriod } = options; - return `${userId}-${orgId || 'user'}-${planId}-${planPeriod}` as CheckoutKey; +function cacheKey(options: { + userId: string; + orgId?: string; + planId: string; + planPeriod: CommerceSubscriptionPlanPeriod; +}): CheckoutKey { + const { userId, orgId, planId, planPeriod } = options; + return JSON.stringify([userId, orgId ?? 'user', planId, planPeriod]) as CheckoutKey; }
40-44
: Avoid unsafe cast; align computed type upstream
computedSignal()
returns a shape withcheckout | null
, but you cast to non-nullCheckoutSignalValue
. Either (a) typecomputedSignal
asCheckoutSignalValue
and ensure non-null before exposure, or (b) allowcheckout | null
in the public type. Recommend (a); see suggested change inCommerceCheckout.ts
.packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)
61-92
: Retry comment doesn’t match configComment says retries at 2s, 4s, 6s, 8s, but
factor: 1.1
withmaxDelayBetweenRetries: 2000
won’t produce those delays. Update the comment or config.
128-141
: Type computed as the public shape and use property shorthandEliminates casts downstream and minor style nit.
- const computedSignal = computed<Omit<CheckoutSignalValue, 'checkout'> & { checkout: CheckoutFutureResource | null }>( - () => { - const resource = resourceSignal().resource; - const error = errorSignal().error; - const fetchStatus = fetchSignal().status; - - const errors = errorsToParsedErrors(error); - return { errors: errors, fetchStatus, checkout: resource }; - }, - ); + const computedSignal = computed<CheckoutSignalValue>(() => { + const resource = resourceSignal().resource; + const error = errorSignal().error; + const fetchStatus = fetchSignal().status; + const errors = errorsToParsedErrors(error); + // `resource` is set by CheckoutFuture ctor before exposure. + return { errors, fetchStatus, checkout: resource as CheckoutFutureResource }; + });
95-126
: Global errors parsing is overly naiveYou push all API errors into
global
, which contradicts the doc “does not include any errors that could be parsed as a field error.” If field-level data is available (e.g., path/param), split accordingly; otherwise, consider keepingglobal: null
when field scoping is unknown.packages/types/src/clerk.ts (1)
91-93
: Dead/ambiguous type: remove or documentCheckoutSignal
Either use this alias for callable signals or remove it to avoid confusion now that the API returns a value.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
packages/clerk-js/src/core/clerk.ts
(2 hunks)packages/clerk-js/src/core/modules/checkout/instance.ts
(3 hunks)packages/clerk-js/src/core/resources/CommerceCheckout.ts
(3 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
(5 hunks)packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
(1 hunks)packages/react/src/stateProxy.ts
(5 hunks)packages/types/src/clerk.ts
(2 hunks)packages/types/src/commerce.ts
(2 hunks)packages/types/src/utils.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/react/src/stateProxy.ts
- packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
- packages/clerk-js/src/core/clerk.ts
- packages/types/src/commerce.ts
- packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{js,jsx,ts,tsx}
: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
packages/**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Packages should export TypeScript types alongside runtime code
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
**/*.{ts,tsx}
: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidany
type - preferunknown
when type is uncertain, then narrow with type guards
Useinterface
for object shapes that might be extended
Usetype
for unions, primitives, and computed types
Preferreadonly
properties for immutable data structures
Useprivate
for internal implementation details
Useprotected
for inheritance hierarchies
Usepublic
explicitly for clarity in public APIs
Preferreadonly
for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Useconst assertions
for literal types:as const
Usesatisfies
operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports:import type { ... } from ...
Noany
types without justification
Proper error handling with typed errors
Consistent use ofreadonly
for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
**/*.{js,ts,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.
Files:
packages/clerk-js/src/core/resources/CommerceCheckout.ts
packages/types/src/utils.ts
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
🧬 Code graph analysis (3)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (4)
packages/types/src/json.ts (1)
CommerceCheckoutJSON
(880-895)packages/types/src/clerk.ts (1)
CheckoutSignalValue
(76-89)packages/types/src/commerce.ts (4)
CheckoutFutureResource
(1412-1427)CheckoutFutureResourceLax
(1429-1446)CreateCheckoutParams
(1136-1155)ConfirmCheckoutParams
(1162-1188)packages/react/src/stateProxy.ts (1)
checkout
(190-196)
packages/clerk-js/src/core/modules/checkout/instance.ts (4)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
CheckoutFuture
(146-223)createSignals
(128-144)packages/clerk-js/src/core/clerk.ts (1)
Clerk
(200-2948)packages/types/src/clerk.ts (3)
Clerk
(165-888)__experimental_CheckoutOptions
(95-99)CheckoutSignalValue
(76-89)packages/react/src/stateProxy.ts (1)
checkout
(190-196)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
FieldError
(7-20)packages/types/src/commerce.ts (3)
CheckoutFutureResource
(1412-1427)ForPayerType
(122-122)CommerceSubscriptionPlanPeriod
(139-139)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Formatting | Dedupe | Changeset
- GitHub Check: Build Packages
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep/ci
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (3)
packages/clerk-js/src/core/modules/checkout/instance.ts (1)
22-55
: Resolved — returning snapshot is correctChecked usages: callers invoke clerk.__experimental_checkout(...) / stateProxy.checkoutSignal(...); none treat the returned value as a callable signal, so returning signals.computedSignal() (a snapshot) matches current consumers.
packages/clerk-js/src/core/resources/CommerceCheckout.ts (1)
225-261
: Operation de-duplication: good patternPending-operation map prevents concurrent duplicate calls from racing. Nicely done.
packages/types/src/clerk.ts (1)
101-101
: Return type alignment looks good
__experimental_CheckoutFunction
returningCheckoutSignalValue
matches the new factory.
useCheckout
with future hooks
6be9212
to
ff4f413
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)
214-220
: Localize aria-label; avoid hard-coded text.Per UI guidelines, localize all user-facing strings, including ARIA.
- <SegmentedControl.Root - aria-label='Payment method source' + <SegmentedControl.Root + aria-label={`${localizationKeys('commerce.aria.paymentMethodSource')}`}If
localizationKeys
isn’t supported for attributes, useuseLocalizations().t('commerce.aria.paymentMethodSource')
.
♻️ Duplicate comments (5)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (3)
353-358
: Derive selected payment source from known list by id.Removes potential stale reference and aligns with past guidance.
- >(paymentSource || paymentSources.find(p => p.isDefault)); + >(() => { + const byId = paymentSource ? paymentSources.find(p => p.id === paymentSource.id) : undefined; + return byId || paymentSources.find(p => p.isDefault); + });
34-38
: Guardtotals
before use to prevent null deref (repeat).
totals
is used unconditionally below (e.g., Lines 78-80, 115-118). Add the guard here.- if (!plan) { + if (!plan || !totals) { return null; }
195-201
: DefaultpaymentSources
to an empty array.Prevents
length
access onundefined
and stabilizes initial state.- const { data: paymentSources } = usePaymentMethods(); + const { data: paymentSources = [] } = usePaymentMethods();packages/clerk-js/src/core/modules/checkout/instance.ts (1)
17-18
: Module-level cache risks leaks across sessions; scope or clear it.Attach cache to the Clerk instance or export a clear function and invoke on sign-out/org change.
-const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>(); +const cache = new Map<CheckoutKey, { resource: CheckoutFuture; signals: ReturnType<typeof createSignals> }>(); + +export const __internal_clearCheckoutCache = () => { + cache.clear(); +};Follow-up: call
__internal_clearCheckoutCache()
on sign out and when the active org changes.packages/types/src/clerk.ts (1)
73-89
: Fix “SignIn” copy/paste in Checkout docs.Update JSDoc to reference Checkout and its resource.
-/** - * The value returned by the `useSignInSignal` hook. - */ +/** + * The value returned by the Checkout signal. + */ export interface CheckoutSignalValue { /** - * Represents the errors that occurred during the last fetch of the parent resource. + * Represents errors from the last fetch of the Checkout resource. */ errors: GlobalErrors; /** - * The fetch status of the underlying `SignIn` resource. + * The fetch status of the underlying Checkout resource. */ fetchStatus: 'idle' | 'fetching'; /** - * An instance representing the currently active `SignIn`, with new APIs designed specifically for custom flows. + * An instance representing the current Checkout, with APIs designed for custom flows. */ checkout: CheckoutFutureResource; }
🧹 Nitpick comments (6)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (3)
78-80
: Unconditional access tototals.*
can crash.This relies on the previous guard. If you prefer rendering a placeholder, gate each group instead.
Also applies to: 115-118
156-163
: Remove@ts-expect-error
; pass unknown cleanly to handler.Keep type-safety without suppressions.
- if (error) { - // @ts-expect-error - error is not an Error - handleError(error, [], card.setError); + if (error) { + handleError(error as unknown, [], card.setError);
167-176
: Validate form data before confirm.
FormData.get
may returnnull
. Add a guard to avoid sending invalid params.const payWithExistingPaymentSource = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); const data = new FormData(e.currentTarget); - const paymentSourceId = data.get('payment_source_id') as string; + const paymentSourceId = data.get('payment_source_id'); + if (!paymentSourceId || typeof paymentSourceId !== 'string') { + return; + } return confirmCheckout({ paymentSourceId, }); };packages/shared/src/react/hooks/useCheckout.ts (2)
40-52
: Stabilize subscribe deps; includeclerk
.Ensure resubscription when the instance changes.
- const subscribe = useCallback( + const subscribe = useCallback( (callback: () => void) => { if (!clerk.loaded) { return () => {}; } return clerk.__internal_state.__internal_effect(() => { signal(); callback(); }); }, - [signal, clerk.loaded, clerk.__internal_state], + [signal, clerk, clerk.loaded, clerk.__internal_state], );
53-60
: Remove cast by typinguseSyncExternalStore
.Cleaner types; no
as
needed.- const getSnapshot = useCallback(() => { - return signal(); - }, [signal]); - - const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot); - return value as CheckoutSignalValue; + const getSnapshot = useCallback<() => CheckoutSignalValue>(() => signal(), [signal]); + const value = useSyncExternalStore<CheckoutSignalValue>(subscribe, getSnapshot, getSnapshot); + return value;packages/clerk-js/src/core/modules/checkout/instance.ts (1)
12-15
: TightenplanPeriod
type in cache key.Use the declared union for safety.
-function cacheKey(options: { userId: string; orgId?: string; planId: string; planPeriod: string }): CheckoutKey { +function cacheKey(options: { userId: string; orgId?: string; planId: string; planPeriod: import('@clerk/types').CommerceSubscriptionPlanPeriod }): CheckoutKey {
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (20)
.changeset/angry-maps-mix.md
(1 hunks)packages/clerk-js/src/core/clerk.ts
(2 hunks)packages/clerk-js/src/core/modules/checkout/__tests__/manager.spec.ts
(0 hunks)packages/clerk-js/src/core/modules/checkout/instance.ts
(3 hunks)packages/clerk-js/src/core/modules/checkout/manager.ts
(0 hunks)packages/clerk-js/src/core/resources/CommerceCheckout.ts
(3 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
(2 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
(5 hunks)packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
(3 hunks)packages/clerk-js/src/ui/components/Checkout/index.tsx
(1 hunks)packages/clerk-js/src/ui/components/Checkout/parts.tsx
(3 hunks)packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
(1 hunks)packages/react/src/isomorphicClerk.ts
(1 hunks)packages/react/src/stateProxy.ts
(5 hunks)packages/shared/src/react/commerce.tsx
(2 hunks)packages/shared/src/react/hooks/__tests__/useCheckout.type.spec.ts
(1 hunks)packages/shared/src/react/hooks/useCheckout.ts
(1 hunks)packages/types/src/clerk.ts
(2 hunks)packages/types/src/commerce.ts
(2 hunks)packages/types/src/utils.ts
(2 hunks)
💤 Files with no reviewable changes (2)
- packages/clerk-js/src/core/modules/checkout/tests/manager.spec.ts
- packages/clerk-js/src/core/modules/checkout/manager.ts
🚧 Files skipped from review as they are similar to previous changes (14)
- .changeset/angry-maps-mix.md
- packages/clerk-js/src/ui/components/PaymentSources/PaymentSourceRow.tsx
- packages/shared/src/react/commerce.tsx
- packages/shared/src/react/hooks/tests/useCheckout.type.spec.ts
- packages/clerk-js/src/ui/components/Checkout/index.tsx
- packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
- packages/types/src/commerce.ts
- packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
- packages/types/src/utils.ts
- packages/clerk-js/src/core/clerk.ts
- packages/react/src/stateProxy.ts
- packages/react/src/isomorphicClerk.ts
- packages/clerk-js/src/core/resources/CommerceCheckout.ts
- packages/clerk-js/src/ui/components/Checkout/parts.tsx
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{js,jsx,ts,tsx}
: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels
Files:
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
packages/**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Packages should export TypeScript types alongside runtime code
Files:
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
**/*.{ts,tsx}
: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidany
type - preferunknown
when type is uncertain, then narrow with type guards
Useinterface
for object shapes that might be extended
Usetype
for unions, primitives, and computed types
Preferreadonly
properties for immutable data structures
Useprivate
for internal implementation details
Useprotected
for inheritance hierarchies
Usepublic
explicitly for clarity in public APIs
Preferreadonly
for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Useconst assertions
for literal types:as const
Usesatisfies
operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports:import type { ... } from ...
Noany
types without justification
Proper error handling with typed errors
Consistent use ofreadonly
for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)
Files:
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
**/*.{js,ts,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.
Files:
packages/shared/src/react/hooks/useCheckout.ts
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
packages/clerk-js/src/core/modules/checkout/instance.ts
packages/types/src/clerk.ts
packages/clerk-js/src/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/clerk-js-ui.mdc)
packages/clerk-js/src/ui/**/*.{ts,tsx}
: Element descriptors should always be camelCase
Use element descriptors in UI components to enable consistent theming and styling via appearance.elements
Element descriptors should generate unique, stable CSS classes for theming
Element descriptors should handle state classes (e.g., cl-loading, cl-active, cl-error, cl-open) automatically based on component state
Do not render hard-coded values; all user-facing strings must be localized using provided localization methods
Use the useLocalizations hook and localizationKeys utility for all text and error messages
Use the styled system (sx prop, theme tokens, responsive values) for custom component styling
Use useCardState for card-level state, useFormState for form-level state, and useLoadingStatus for loading states
Always use handleError utility for API errors and use translateError for localized error messages
Use useFormControl for form field state, implement proper validation, and handle loading and error states in forms
Use localization keys for all form labels and placeholders
Use element descriptors for consistent styling and follow the theme token system
Use the Card and FormContainer patterns for consistent UI structure
Files:
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{jsx,tsx}
: Use error boundaries in React components
Minimize re-renders in React components
**/*.{jsx,tsx}
: Always use functional components with hooks instead of class components
Follow PascalCase naming for components:UserProfile
,NavigationMenu
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Use useState for simple state management
Use useReducer for complex state logic
Implement proper state initialization
Use proper state updates with callbacks
Implement proper state cleanup
Use Context API for theme/authentication
Implement proper state selectors
Use proper state normalization
Implement proper state persistence
Use React.memo for expensive components
Implement proper useCallback for handlers
Use proper useMemo for expensive computations
Implement proper virtualization for lists
Use proper code splitting with React.lazy
Implement proper cleanup in useEffect
Use proper refs for DOM access
Implement proper event listener cleanup
Use proper abort controllers for fetch
Implement proper subscription cleanup
Use proper HTML elements
Implement proper ARIA attributes
Use proper heading hierarchy
Implement proper form labels
Use proper button types
Implement proper focus management
Use proper keyboard shortcuts
Implement proper tab order
Use proper skip links
Implement proper focus traps
Implement proper error boundaries
Use proper error logging
Implement proper error recovery
Use proper error messages
Implement proper error fallbacks
Use proper form validation
Implement proper error states
Use proper error messages
Implement proper form submission
Use proper form reset
Use proper component naming
Implement proper file naming
Use proper prop naming
Implement proper...
Files:
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/react.mdc)
**/*.tsx
: Use proper type definitions for props and state
Leverage TypeScript's type inference where possible
Use proper event types for handlers
Implement proper generic types for reusable components
Use proper type guards for conditional rendering
Files:
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
🧬 Code graph analysis (4)
packages/shared/src/react/hooks/useCheckout.ts (3)
packages/shared/src/react/contexts.tsx (3)
__experimental_CheckoutProvider
(119-119)useCheckoutContext
(118-118)useClerkInstanceContext
(117-117)packages/types/src/clerk.ts (1)
CheckoutSignalValue
(76-89)packages/clerk-js/src/ui/contexts/components/Checkout.ts (1)
useCheckoutContext
(10-37)
packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (7)
packages/react/src/stateProxy.ts (1)
checkout
(191-197)packages/clerk-js/src/core/resources/CommerceCheckout.ts (3)
plan
(170-172)totals
(176-178)paymentSource
(189-191)packages/types/src/commerce.ts (2)
ConfirmCheckoutParams
(1162-1188)CommercePaymentSourceResource
(487-534)packages/clerk-js/src/ui/utils/errorHandler.ts (1)
handleError
(69-91)packages/clerk-js/src/ui/contexts/components/Plans.tsx (1)
usePaymentMethods
(32-40)packages/shared/src/react/hooks/usePaymentMethods.tsx (1)
usePaymentMethods
(9-21)packages/types/src/utils.ts (1)
RemoveFunctions
(127-129)
packages/clerk-js/src/core/modules/checkout/instance.ts (3)
packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)
CheckoutFuture
(146-223)createSignals
(128-144)packages/clerk-js/src/core/clerk.ts (1)
Clerk
(200-2948)packages/types/src/clerk.ts (3)
Clerk
(165-888)__experimental_CheckoutOptions
(95-99)CheckoutSignalValue
(76-89)
packages/types/src/clerk.ts (2)
packages/types/src/state.ts (1)
FieldError
(7-20)packages/types/src/commerce.ts (3)
CheckoutFutureResource
(1412-1427)ForPayerType
(122-122)CommerceSubscriptionPlanPeriod
(139-139)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
- GitHub Check: Integration Tests (react-router, chrome)
- GitHub Check: Integration Tests (express, chrome)
- GitHub Check: Integration Tests (sessions, chrome)
- GitHub Check: Integration Tests (custom, chrome)
- GitHub Check: Integration Tests (machine, chrome)
- GitHub Check: Integration Tests (billing, chrome)
- GitHub Check: Integration Tests (expo-web, chrome)
- GitHub Check: Integration Tests (vue, chrome)
- GitHub Check: Integration Tests (nuxt, chrome)
- GitHub Check: Integration Tests (astro, chrome)
- GitHub Check: Static analysis
- GitHub Check: Integration Tests (quickstart, chrome)
- GitHub Check: Integration Tests (tanstack-react-router, chrome)
- GitHub Check: Integration Tests (localhost, chrome)
- GitHub Check: Integration Tests (nextjs, chrome, 15)
- GitHub Check: Integration Tests (tanstack-react-start, chrome)
- GitHub Check: Integration Tests (ap-flows, chrome)
- GitHub Check: Integration Tests (generic, chrome)
- GitHub Check: Integration Tests (elements, chrome)
- GitHub Check: Unit Tests (18, --filter=@clerk/astro --filter=@clerk/backend --filter=@clerk/express --filter=@c...
- GitHub Check: Unit Tests (22, **)
- GitHub Check: Publish with pkg-pr-new
- GitHub Check: Formatting | Dedupe | Changeset
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (2)
packages/clerk-js/src/core/modules/checkout/instance.ts (1)
40-44
: Return cached signal value is fine; ensure signals remain hot.LGTM assuming callers subscribe via
__internal_state
. No change needed.packages/types/src/clerk.ts (1)
101-101
: Return type alignment looks good.
__experimental_CheckoutFunction
now returnsCheckoutSignalValue
; consistent with the new API.
if (checkout.status !== 'needs_confirmation') { | ||
throw new Error('Checkout not found'); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid throwing from hook logic; fail gracefully.
Throwing here will crash the tree when the status is not confirmable. Either gate rendering upstream or handle gracefully in the mutation.
- if (checkout.status !== 'needs_confirmation') {
- throw new Error('Checkout not found');
- }
+ const ensureConfirmable = () => {
+ return checkout.status === 'needs_confirmation';
+ };
And inside confirmCheckout
:
- const confirmCheckout = async (params: ConfirmCheckoutParams) => {
+ const confirmCheckout = async (params: ConfirmCheckoutParams) => {
card.setLoading();
card.setError(undefined);
+ if (!ensureConfirmable()) {
+ card.setIdle();
+ return;
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (checkout.status !== 'needs_confirmation') { | |
throw new Error('Checkout not found'); | |
} | |
const ensureConfirmable = () => { | |
return checkout.status === 'needs_confirmation'; | |
}; |
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx around lines
148–151, do not throw an Error when checkout.status !== 'needs_confirmation';
instead fail gracefully by returning early or setting a local error/loading
state and render a fallback UI (or no-op) so the component tree doesn't crash;
likewise update confirmCheckout to avoid throwing synchronously—have it return a
rejected promise or set/return an error result that the caller/mutation can
handle, and ensure the upstream render is gated by a status check or consumes
the returned error state to show a proper message.
const signal = useCallback(() => { | ||
return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization }); | ||
}, [ | ||
// user?.id, organization?.id, | ||
planId, | ||
planPeriod, | ||
forOrganization, | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Include clerk
in deps to avoid stale closure and pass ESLint.
clerk
is captured; add it to the dependency array.
- }, [
+ }, [
// user?.id, organization?.id,
planId,
planPeriod,
forOrganization,
+ clerk,
]);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const signal = useCallback(() => { | |
return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization }); | |
}, [ | |
// user?.id, organization?.id, | |
planId, | |
planPeriod, | |
forOrganization, | |
]); | |
const signal = useCallback(() => { | |
return clerk.__experimental_checkout({ planId, planPeriod, for: forOrganization }); | |
}, [ | |
// user?.id, organization?.id, | |
planId, | |
planPeriod, | |
forOrganization, | |
clerk, | |
]); |
🤖 Prompt for AI Agents
In packages/shared/src/react/hooks/useCheckout.ts around lines 31 to 38, the
useCallback captures the `clerk` variable but it is not listed in the dependency
array; update the dependency array to include `clerk` (e.g. [clerk, planId,
planPeriod, forOrganization]) so the callback references the latest clerk and
satisfies ESLint.
interface GlobalErrors { | ||
/** | ||
* The raw, unparsed errors from the Clerk API. | ||
*/ | ||
raw: FieldError[] | null; | ||
/** | ||
* Parsed errors that are not related to any specific field. | ||
*/ | ||
global: FieldError[] | null; // does not include any errors that could be parsed as a field error | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Export GlobalErrors
used in public API.
Referenced by exported CheckoutSignalValue
; export it to avoid opaque types in d.ts.
-interface GlobalErrors {
+export interface GlobalErrors {
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
interface GlobalErrors { | |
/** | |
* The raw, unparsed errors from the Clerk API. | |
*/ | |
raw: FieldError[] | null; | |
/** | |
* Parsed errors that are not related to any specific field. | |
*/ | |
global: FieldError[] | null; // does not include any errors that could be parsed as a field error | |
} | |
export interface GlobalErrors { | |
/** | |
* The raw, unparsed errors from the Clerk API. | |
*/ | |
raw: FieldError[] | null; | |
/** | |
* Parsed errors that are not related to any specific field. | |
*/ | |
global: FieldError[] | null; // does not include any errors that could be parsed as a field error | |
} |
🤖 Prompt for AI Agents
In packages/types/src/clerk.ts around lines 62 to 71, the interface GlobalErrors
is currently unexported but is referenced by an exported type
(CheckoutSignalValue), causing opaque types in the generated d.ts; fix it by
adding an export modifier to the interface declaration (export interface
GlobalErrors { ... }) so it becomes part of the public API, and update any
imports/usages if necessary to use the exported name.
function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] { | ||
const parsedErrors: CheckoutSignalValue['errors'] = { | ||
raw: null, | ||
global: null, | ||
}; | ||
|
||
if (!error) { | ||
return parsedErrors; | ||
} | ||
|
||
if (!isClerkAPIResponseError(error)) { | ||
parsedErrors.raw = []; | ||
parsedErrors.global = []; | ||
return parsedErrors; | ||
} | ||
|
||
error.errors.forEach(error => { | ||
if (parsedErrors.raw) { | ||
parsedErrors.raw.push(error); | ||
} else { | ||
parsedErrors.raw = [error]; | ||
} | ||
|
||
if (parsedErrors.global) { | ||
parsedErrors.global.push(error); | ||
} else { | ||
parsedErrors.global = [error]; | ||
} | ||
}); | ||
|
||
return parsedErrors; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error structure is WIP
__experimental_checkout(options: __experimental_CheckoutOptions): CheckoutSignalValue { | ||
if (!this._checkout) { | ||
this._checkout = params => createCheckoutInstance(this, params); | ||
this._checkout = (params: any) => createCheckoutInstance(this, params); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this params possible to be typed?
subscribe, | ||
getState: manager.getCacheState, | ||
}; | ||
const checkout = new CheckoutFuture(signals, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not saying you should do it this way, but in our SignInFuture class we don't directly access signals. The resources emit events and our state layer listens for those to propagate to the signals.
}; | ||
} | ||
|
||
function errorsToParsedErrors(error: unknown): CheckoutSignalValue['errors'] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just calling out we don't store this implementation alongside the resource, but that's because we have a dedicated signals file. not saying you need to move it, just wanted to point that out in case you can think of a better place that more closely aligns with signIn/signUp.
} | ||
|
||
async start(): Promise<{ error: unknown }> { | ||
return this.runAsyncResourceTask( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not loving that this is a slightly different implementation of runAsyncResourceTask
. Can we come up with a single implantation that works for both use cases?
const { error } = await checkout.confirm(params); | ||
|
||
if (error) { | ||
// @ts-expect-error - error is not an Error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what TS rule was this breaking?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because Error is unknown
return gateProperty(target, 'externalClientSecret', null) as null; | ||
}, | ||
get externalGatewayId() { | ||
return gateProperty(target, 'externalGatewayId', null) as null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are these casts necessary? the types should be pulling through gateProperty
correctly.
import type { WaitlistResource } from './waitlist'; | ||
|
||
type __experimental_CheckoutStatus = 'needs_initialization' | 'needs_confirmation' | 'completed'; | ||
interface GlobalErrors { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what are your thoughts on putting this into the ./state
file?
Description
Vanilla JS
React
Checklist
pnpm test
runs as expected.pnpm build
runs as expected.Type of change
Summary by CodeRabbit
New Features
Refactor
Tests
Chores