Skip to main content

Errors and failures

Understand the shared Convex error categories and who owns recovery.

Better Convex Nuxt exposes one public call error across client composables and server helpers: ConvexCallError.

Error kinds

KindMeaningTypical owner of recovery
authenticationRequired identity is missing, token exchange failed with auth status, or identity changed during a callAuth UI or operation caller
transportNetwork, timeout, abort, malformed response, or library-owned HTTP boundary failureRetry/offline UI or operator
serverA structured Convex application/function errorApplication workflow
unknownNo stable mechanical classification is availableFallback handling and diagnostics

There is no generic validation kind. The module does not infer categories from message text.

Every error also carries functionName, the Convex function path such as projects:update, when the failing call knows it.

Stable codes

Failures that Better Convex raises itself carry a stable code:

CodeMeaning
IDENTITY_CHANGEDThe signed-in identity changed before or while the call was sent
CANCELLEDcancel(), reset(), or a disposed scope stopped the work
FILE_TOO_LARGEAn upload is larger than maxSize
FILE_TYPE_NOT_ALLOWEDAn upload does not match allowedTypes
UPLOAD_IN_PROGRESSA second upload started while the first one is pending
SUBMIT_IN_PROGRESSA second form submission started while the first one is pending
UNAUTHENTICATEDA server operation requires a signed-in identity
CLIENT_UNAVAILABLENo browser Convex client exists, for example during server rendering

Application errors keep the code that your Convex function puts in ConvexError data. Check both kinds of code with isConvexCallError(error, code).

Was the request sent?

A browser write can fail before it is sent, or after it was sent but before its result arrived. The library records which one in outcome:

  • 'not-sent': the request never reached Convex. Nothing was written.
  • 'unknown': the request was sent, and no result arrived. The write may have happened.
  • undefined: the failure is not about sending, for example Convex rejected the call with a ConvexError.

outcome comes from what the library did with the request. It is never guessed from the code. Uploads also carry phase, the upload step that failed. See Error types.

One rejected-Promise contract

mutate and run reject with ConvexCallError:

ts
import { isConvexCallError, normalizeConvexError } from '@lupinum/better-convex-nuxt/errors'

const { mutate: saveProject } = useConvexMutation(api.projects.update)

try {
  await saveProject({ projectId, name })
} catch (error) {
  if (isConvexCallError(error, 'PROJECT_ARCHIVED')) {
    showArchivedNotice()
    return
  }
  reportFailure(normalizeConvexError(error).kind)
}

useConvexForm resolves expected validation and mutation failures as { ok: false, error } instead. Callers decide how to project a caught error into local UI state.

Domain meaning belongs to the application

A Convex function can throw structured ConvexError data such as:

ts
throw new ConvexError({
  code: 'PROJECT_ARCHIVED',
  message: 'Archived projects cannot be edited',
})

The module classifies this as server, keeps data unchanged, and copies data.code to code. The message is the text you wrote: a string data, else data.message, else the generic Convex application error. Your application decides that PROJECT_ARCHIVED disables editing or redirects elsewhere.

Write data.message for the person who reads it, because components usually show error.message. Do not put secrets or internal details in it. Use code for decisions. Do not parse the message to discover business meaning.

Convex's own wire message is never used, because it can contain function stack frames. Unclassified strings, objects, and errors use Unknown Convex error. This rule is identical in browser, SSR, and server calls.

Serialized errors

SSR errors cross the Nuxt payload as a public serialized shape. The client revives that shape as ConvexCallError, including functionName.

A Nitro route can return the same shape, without functionName, with toConvexH3Error(error). In the browser, normalizeConvexError() revives it from the FetchError of $fetch or useFetch. Revival accepts only the exact public shape; any other object stays unknown.

ConvexCallError does not retain a raw upstream cause. JSON, structured clone, SSR HTML, normal inspection, and direct property access therefore cannot recover an upstream response body, credential, stack, or request object from the public error. Use the public fields for product behavior. For server logs, add your own operation name and request ID.

Retry is contextual

  • Retry a transient transport failure when the operation is safe to repeat.
  • Do not retry authentication failures until auth state changes.
  • Let the product workflow decide whether a server error is retryable.
  • Do not retry a write with outcome: 'unknown' blindly; it may have reached the server. Make the write idempotent first.

See error handling for UI patterns and credentials and security for server-boundary controls.