From d6e8f32182fe894f22a8cea0fbfedb3548ecfb54 Mon Sep 17 00:00:00 2001 From: Kenton Duprey Date: Thu, 18 Dec 2025 05:53:46 -0500 Subject: [PATCH 1/5] refactor(shared): Update exported error typeguard functions from error.ts, align error class naming (#7490) --- .changeset/sweet-singers-beg.md | 5 +++ packages/shared/src/error.ts | 10 +++--- packages/shared/src/errors/clerkApiError.ts | 9 +++--- .../src/errors/clerkApiResponseError.ts | 8 ++--- packages/shared/src/errors/globalHookError.ts | 4 +-- packages/shared/src/errors/helpers.ts | 31 ++----------------- 6 files changed, 22 insertions(+), 45 deletions(-) create mode 100644 .changeset/sweet-singers-beg.md diff --git a/.changeset/sweet-singers-beg.md b/.changeset/sweet-singers-beg.md new file mode 100644 index 00000000000..61b5eb99954 --- /dev/null +++ b/.changeset/sweet-singers-beg.md @@ -0,0 +1,5 @@ +--- +'@clerk/shared': patch +--- + +Refactor internal Clerk error handling functions diff --git a/packages/shared/src/error.ts b/packages/shared/src/error.ts index ad8cea5ce3b..3f034067234 100644 --- a/packages/shared/src/error.ts +++ b/packages/shared/src/error.ts @@ -1,8 +1,8 @@ export { errorToJSON, parseError, parseErrors } from './errors/parseError'; -export { ClerkAPIError } from './errors/clerkApiError'; -export { ClerkAPIResponseError } from './errors/clerkApiResponseError'; -export { ClerkError } from './errors/clerkError'; +export { ClerkAPIError, isClerkAPIError } from './errors/clerkApiError'; +export { ClerkAPIResponseError, isClerkAPIResponseError } from './errors/clerkApiResponseError'; +export { ClerkError, isClerkError } from './errors/clerkError'; export { buildErrorThrower, type ErrorThrower, type ErrorThrowerOptions } from './errors/errorThrower'; @@ -10,15 +10,13 @@ export { EmailLinkError, EmailLinkErrorCode, EmailLinkErrorCodeStatus } from './ export type { MetamaskError } from './errors/metamaskError'; -export { ClerkRuntimeError } from './errors/clerkRuntimeError'; +export { ClerkRuntimeError, isClerkRuntimeError } from './errors/clerkRuntimeError'; export { ClerkWebAuthnError } from './errors/webAuthNError'; export { is4xxError, isCaptchaError, - isClerkAPIResponseError, - isClerkRuntimeError, isEmailLinkError, isKnownError, isMetamaskError, diff --git a/packages/shared/src/errors/clerkApiError.ts b/packages/shared/src/errors/clerkApiError.ts index 42fc43183ee..f06339df7c7 100644 --- a/packages/shared/src/errors/clerkApiError.ts +++ b/packages/shared/src/errors/clerkApiError.ts @@ -1,12 +1,13 @@ import type { ClerkAPIError as ClerkAPIErrorInterface, ClerkAPIErrorJSON } from '../types'; import { createErrorTypeGuard } from './createErrorTypeGuard'; -export type ClerkApiErrorMeta = Record; +export type ClerkAPIErrorMeta = Record; /** * This error contains the specific error message, code, and any additional metadata that was returned by the Clerk API. */ -export class ClerkAPIError implements ClerkAPIErrorInterface { +export class ClerkAPIError implements ClerkAPIErrorInterface { + // TODO: Update kind to match class name in Core 3 static kind = 'ClerkApiError'; readonly code: string; readonly message: string; @@ -36,6 +37,6 @@ export class ClerkAPIError implements Cler } /** - * Type guard to check if a value is a ClerkApiError instance. + * Type guard to check if a value is a ClerkAPIError instance. */ -export const isClerkApiError = createErrorTypeGuard(ClerkAPIError); +export const isClerkAPIError = createErrorTypeGuard(ClerkAPIError); diff --git a/packages/shared/src/errors/clerkApiResponseError.ts b/packages/shared/src/errors/clerkApiResponseError.ts index 671c41bef5c..edf9dc04407 100644 --- a/packages/shared/src/errors/clerkApiResponseError.ts +++ b/packages/shared/src/errors/clerkApiResponseError.ts @@ -47,14 +47,14 @@ export class ClerkAPIResponseError extends ClerkError implements ClerkAPIRespons } /** - * Type guard to check if an error is a ClerkApiResponseError. + * Type guard to check if an error is a ClerkAPIResponseError. * Can be called as a standalone function or as a method on an error object. * * @example * // As a standalone function - * if (isClerkApiResponseError(error)) { ... } + * if (isClerkAPIResponseError(error)) { ... } * * // As a method (when attached to error object) - * if (error.isClerkApiResponseError()) { ... } + * if (error.isClerkAPIResponseError()) { ... } */ -export const isClerkApiResponseError = createErrorTypeGuard(ClerkAPIResponseError); +export const isClerkAPIResponseError = createErrorTypeGuard(ClerkAPIResponseError); diff --git a/packages/shared/src/errors/globalHookError.ts b/packages/shared/src/errors/globalHookError.ts index c4615e16234..c02c813b443 100644 --- a/packages/shared/src/errors/globalHookError.ts +++ b/packages/shared/src/errors/globalHookError.ts @@ -1,4 +1,4 @@ -import { isClerkApiResponseError } from './clerkApiResponseError'; +import { isClerkAPIResponseError } from './clerkApiResponseError'; import type { ClerkError } from './clerkError'; import { isClerkRuntimeError } from './clerkRuntimeError'; @@ -9,7 +9,7 @@ import { isClerkRuntimeError } from './clerkRuntimeError'; */ export function createClerkGlobalHookError(error: ClerkError) { const predicates = { - isClerkApiResponseError, + isClerkAPIResponseError, isClerkRuntimeError, } as const; diff --git a/packages/shared/src/errors/helpers.ts b/packages/shared/src/errors/helpers.ts index 5d28681e836..b95f55d5a8c 100644 --- a/packages/shared/src/errors/helpers.ts +++ b/packages/shared/src/errors/helpers.ts @@ -1,5 +1,7 @@ import type { ClerkAPIResponseError } from './clerkApiResponseError'; +import { isClerkAPIResponseError } from './clerkApiResponseError'; import type { ClerkRuntimeError } from './clerkRuntimeError'; +import { isClerkRuntimeError } from './clerkRuntimeError'; import type { EmailLinkError } from './emailLinkError'; import type { MetamaskError } from './metamaskError'; @@ -55,35 +57,6 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error); } -/** - * Checks if the provided error is a ClerkAPIResponseError. - * - * @internal - */ -export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError { - return err && 'clerkError' in err; -} - -/** - * Checks if the provided error object is an instance of ClerkRuntimeError. - * - * @param err - The error object to check. - * @returns True if the error is a ClerkRuntimeError, false otherwise. - * - * @example - * const error = new ClerkRuntimeError('An error occurred'); - * if (isClerkRuntimeError(error)) { - * // Handle ClerkRuntimeError - * console.error('ClerkRuntimeError:', error.message); - * } else { - * // Handle other errors - * console.error('Other error:', error.message); - * } - */ -export function isClerkRuntimeError(err: any): err is ClerkRuntimeError { - return 'clerkRuntimeError' in err; -} - /** * Checks if the provided error is a Clerk runtime error indicating a reverification was cancelled. * From 74aacfd5af5c25ea462274a7da31f79a1fad1fea Mon Sep 17 00:00:00 2001 From: Kenton Duprey Date: Thu, 18 Dec 2025 12:20:39 -0500 Subject: [PATCH 2/5] fix(errors): Update ClerkAPIError.kind to match class name and add codemod for breaking change Signed-off-by: Kenton Duprey --- packages/shared/src/errors/clerkApiError.ts | 3 +-- .../changes/clerk-api-error-kind-added.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 packages/upgrade/src/versions/core-3/changes/clerk-api-error-kind-added.md diff --git a/packages/shared/src/errors/clerkApiError.ts b/packages/shared/src/errors/clerkApiError.ts index f06339df7c7..4daf5fa329b 100644 --- a/packages/shared/src/errors/clerkApiError.ts +++ b/packages/shared/src/errors/clerkApiError.ts @@ -7,8 +7,7 @@ export type ClerkAPIErrorMeta = Record; * This error contains the specific error message, code, and any additional metadata that was returned by the Clerk API. */ export class ClerkAPIError implements ClerkAPIErrorInterface { - // TODO: Update kind to match class name in Core 3 - static kind = 'ClerkApiError'; + static kind = 'ClerkAPIError'; readonly code: string; readonly message: string; readonly longMessage: string | undefined; diff --git a/packages/upgrade/src/versions/core-3/changes/clerk-api-error-kind-added.md b/packages/upgrade/src/versions/core-3/changes/clerk-api-error-kind-added.md new file mode 100644 index 00000000000..8d5df165f4e --- /dev/null +++ b/packages/upgrade/src/versions/core-3/changes/clerk-api-error-kind-added.md @@ -0,0 +1,17 @@ +--- +title: '`ClerkAPIError.kind` value updated' +matcher: 'ClerkApiError' +category: 'behavior-change' +warning: true +--- + +`ClerkAPIError.kind` has been updated to match the class name: + +```diff +- static kind = 'ClerkApiError' ++ static kind = 'ClerkAPIError' +``` + +Most users should not be affected. If you were checking this string directly (for example, `error.constructor.kind === 'ClerkApiError'`), update the comparison value. + +No other changes are required. From 696ea440877ff999ba2b42eb3693a329990c3602 Mon Sep 17 00:00:00 2001 From: Kenton Duprey Date: Thu, 18 Dec 2025 12:44:26 -0500 Subject: [PATCH 3/5] chore: pnpm changeset Signed-off-by: Kenton Duprey --- .changeset/tall-games-design.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/tall-games-design.md diff --git a/.changeset/tall-games-design.md b/.changeset/tall-games-design.md new file mode 100644 index 00000000000..21cc63f76eb --- /dev/null +++ b/.changeset/tall-games-design.md @@ -0,0 +1,6 @@ +--- +'@clerk/shared': major +'@clerk/upgrade': minor +--- + +Update `ClerkAPIError.kind` value to match class name From 2ac819817f03a15a6c98e9b6acdaebe6f27ada02 Mon Sep 17 00:00:00 2001 From: Kenton Duprey Date: Thu, 18 Dec 2025 17:45:23 -0500 Subject: [PATCH 4/5] fix(errors): Enhance type guard to handle cross-bundle scenarios with 'kind' property Signed-off-by: Kenton Duprey --- packages/shared/src/errors/createErrorTypeGuard.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/shared/src/errors/createErrorTypeGuard.ts b/packages/shared/src/errors/createErrorTypeGuard.ts index 842e8d4e8c3..4f3d30e1f60 100644 --- a/packages/shared/src/errors/createErrorTypeGuard.ts +++ b/packages/shared/src/errors/createErrorTypeGuard.ts @@ -27,6 +27,14 @@ export function createErrorTypeGuard Value>( if (!target) { throw new TypeError(`${ErrorClass.kind || ErrorClass.name} type guard requires an error object`); } + // Use duck-typing with 'kind' property to handle cross-bundle scenarios + // where instanceof fails due to different class instances + if (ErrorClass.kind && typeof target === 'object' && target !== null && 'constructor' in target) { + const targetConstructor = (target as { constructor?: { kind?: string } }).constructor; + if (targetConstructor?.kind === ErrorClass.kind) { + return true; + } + } return target instanceof ErrorClass; } From b4ad421018bc9ed2e5d5dfd6660c827ee0f7c22e Mon Sep 17 00:00:00 2001 From: Kenton Duprey Date: Fri, 19 Dec 2025 12:22:38 -0500 Subject: [PATCH 5/5] fix(errors): Correct casing of 'ClerkAPIResponseError' in error handling Signed-off-by: Kenton Duprey --- packages/ui/src/components/Checkout/CheckoutPage.tsx | 2 +- packages/ui/src/components/Checkout/parts.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Checkout/CheckoutPage.tsx b/packages/ui/src/components/Checkout/CheckoutPage.tsx index acfe6c84738..220b1f036bd 100644 --- a/packages/ui/src/components/Checkout/CheckoutPage.tsx +++ b/packages/ui/src/components/Checkout/CheckoutPage.tsx @@ -62,7 +62,7 @@ const FetchStatus = ({ const internalFetchStatus = useMemo(() => { if (errors.global) { const errorCodes = errors.global.flatMap(e => { - if (e.isClerkApiResponseError()) { + if (e.isClerkAPIResponseError()) { return e.errors.map(e => e.code); } }); diff --git a/packages/ui/src/components/Checkout/parts.tsx b/packages/ui/src/components/Checkout/parts.tsx index 8a0a882ce59..5bd81d4eb74 100644 --- a/packages/ui/src/components/Checkout/parts.tsx +++ b/packages/ui/src/components/Checkout/parts.tsx @@ -42,7 +42,7 @@ export const InvalidPlanScreen = () => { const { errors } = useCheckout(); const InvalidPlanError = errors?.global - ?.filter(e => e.isClerkApiResponseError()) + ?.filter(e => e.isClerkAPIResponseError()) .flatMap(e => e.errors) .find(e => e.code === 'invalid_plan_change');