import { ConvexError } from "convex/values"; import { QueryCtx, MutationCtx, ActionCtx } from "../_generated/server"; export const DEV_USER_ID = "test-user-001"; /** * True only when explicitly running `convex dev` (which sets CONVEX_IS_DEV). * Never infer dev mode from missing env vars — that would make production * behave like dev if CONVEX_CLOUD_URL happens to be unset. */ export const isDev = process.env.CONVEX_IS_DEV === "true"; /** * Returns the current user's ID, or null if unauthenticated. * * Resolution order: * 1. Real auth identity from Clerk/Convex auth (ctx.auth.getUserIdentity) * 2. Dev-only fallback to test-user-001 (only when CONVEX_IS_DEV=true) * * This is the sole entry point for resolving the current user — * no Convex function should call auth APIs directly. */ export async function resolveUserId( ctx: QueryCtx | MutationCtx | ActionCtx, ): Promise { const identity = await ctx.auth.getUserIdentity(); if (identity?.subject) { return identity.subject; } if (isDev) { return DEV_USER_ID; } return null; } /** * Returns the full user identity (name, email, etc.) or null. * Use when you need more than just the user ID (e.g., checkout prefill). */ export async function resolveUserIdentity( ctx: QueryCtx | MutationCtx | ActionCtx, ): Promise<{ subject: string; name?: string; givenName?: string; familyName?: string; email?: string } | null> { const identity = await ctx.auth.getUserIdentity(); if (identity?.subject) return identity; return null; } /** * Returns the current user's ID or throws if unauthenticated. * Use for mutations/actions that always require auth. */ export async function requireUserId( ctx: QueryCtx | MutationCtx | ActionCtx, ): Promise { const userId = await resolveUserId(ctx); if (!userId) { // Throw as ConvexError so Convex's server-side Sentry integration treats it // as an expected business error (WebSocket/auth races on query fire) rather // than reporting every unauthed query fire as an unhandled exception // (WORLDMONITOR-N3). throw new ConvexError("AUTH_REQUIRED"); } return userId; }