Correction de l'erreur Better Auth 'Model session not found in schema'
1. Description du Problème
Section titled “1. Description du Problème”Lors des appels aux endpoints d’authentification (/api/auth/sign-in/email, /api/auth/dash/*), le worker Cloudflare amn-dev ou le serveur local renvoyait l’erreur critique suivante :
ERROR [Better Auth]: BetterAuthError BetterAuthError: Model "session" not found in schema# SERVER_ERROR: BetterAuthError: Model "session" not found in schema2. Analyse des Causes Racines
Section titled “2. Analyse des Causes Racines”L’investigation approfondie de @better-auth/core (getDefaultModelName.mjs), @better-auth/drizzle-adapter et du plugin dash() (@better-auth/infra) a révélé deux causes racines combinées :
Cause 1 : Module Namespace vs Objet de Schéma Destructuré dans drizzleAdapter
Section titled “Cause 1 : Module Namespace vs Objet de Schéma Destructuré dans drizzleAdapter”Better Auth résout les noms des modèles internes (user, session, account, verification) en recherchant les clés correspondantes dans l’objet schema fourni à l’adaptateur :
const getDefaultModelName = (model) => { const resolve = (candidate) => { if (schema[candidate]) return candidate; return Object.entries(schema).find(([_, f]) => f.modelName === candidate)?.[0]; }; const m = resolve(model); if (!m) throw new BetterAuthError(`Model "${model}" not found in schema`); return m;};Lorsque le schéma est importé via import * as schema from "../db/schema", l’objet transmis est un ES Module Namespace contenant des métadonnées de module et des objets relations Drizzle en premier niveau. Cela perturbe la résolution de getDefaultModelName dans l’adaptateur Drizzle.
Cause 2 : Interaction entre secondaryStorage (KV) et le plugin dash()
Section titled “Cause 2 : Interaction entre secondaryStorage (KV) et le plugin dash()”Lorsque secondaryStorage (Cloudflare KV via createKVStorage(env.AUTH_KV)) est activé sans l’option storeSessionInDatabase: true :
- Better Auth délègue la gestion des sessions au KV et désactive l’enregistrement du modèle
sessiondans le schéma de l’adaptateur de base de données (D1). - Cependant, le plugin
dash()(Better Auth Infrastructure, activé lorsqueBETTER_AUTH_API_KEYest présent sur Cloudflare) effectue des appels internes de gestion et d’analyse sur l’adaptateur DB (ctx.context.adapter.findMany({ model: "session" })). - L’adaptateur DB ne trouvant pas le modèle
sessiondans son schéma interne, lève immédiatement l’exceptionModel "session" not found in schema.
3. Solution Appliquée (DOC → SPEC → CODE → TEST → INTÉG)
Section titled “3. Solution Appliquée (DOC → SPEC → CODE → TEST → INTÉG)”La correction a été appliquée dans apps/web/src/auth/index.ts :
A. Passage propre du schéma Drizzle
Section titled “A. Passage propre du schéma Drizzle”Importation déstructurée des tables et relations Drizzle :
import { user, session, account, verification, userRelations, sessionRelations, accountRelations} from "../db/schema";B. Activation de storeSessionInDatabase: true
Section titled “B. Activation de storeSessionInDatabase: true”Garantit la présence continue du modèle session dans le schéma de l’adaptateur DB même si Cloudflare KV est utilisé en stockage secondaire :
session: { modelName: "session", expiresIn: 60 * 60 * 24 * 7, // 7 jours updateAge: 60 * 60 * 24, // 1 jour storeSessionInDatabase: true // Maintenance du schéma DB pour le plugin dash()},C. Déclaration explicite des modelName
Section titled “C. Déclaration explicite des modelName”Déclaration explicite des noms de modèles dans les options globales de betterAuth :
user: { modelName: "user" },session: { modelName: "session", ... },account: { modelName: "account", accountLinking: { enabled: false } },verification: { modelName: "verification" }Code complet dans apps/web/src/auth/index.ts
Section titled “Code complet dans apps/web/src/auth/index.ts”export function createAuth(env: any) { const db = drizzle(env.AUTH_DB, { schema: { user, session, account, verification } });
const isProd = env.ENVIRONMENT === 'prod'; const trustedOriginsEnv = env.BETTER_AUTH_TRUSTED_ORIGINS || ''; const trustedOrigins = trustedOriginsEnv ? trustedOriginsEnv.split(',').map((o: string) => o.trim()) : [];
if (!isProd) { trustedOrigins.push('http://localhost:4321', 'http://localhost:8787'); }
const plugins: any[] = [admin()];
if (env.BETTER_AUTH_API_KEY) { plugins.push(dash({ apiKey: env.BETTER_AUTH_API_KEY, activityTracking: { enabled: true, updateInterval: 300000 // 5 minutes } })); }
return betterAuth({ database: drizzleAdapter(db, { provider: "sqlite", schema: { user, session, account, verification, userRelations, sessionRelations, accountRelations, }, usePlural: false }), secondaryStorage: env.AUTH_KV ? createKVStorage(env.AUTH_KV) : undefined, baseURL: env.BETTER_AUTH_URL || (isProd ? 'https://amn-dev.anderson-metsanou.workers.dev/api/auth' : 'http://localhost:8787/api/auth'), trustedOrigins, user: { modelName: "user", }, session: { modelName: "session", expiresIn: 60 * 60 * 24 * 7, updateAge: 60 * 60 * 24, storeSessionInDatabase: true }, account: { modelName: "account", accountLinking: { enabled: false } }, verification: { modelName: "verification", }, emailAndPassword: { enabled: true, requireEmailVerification: false }, plugins });}4. Tests et Validation Empirique
Section titled “4. Tests et Validation Empirique”A. Contrôle de Qualité Code
Section titled “A. Contrôle de Qualité Code”task typecheck # 0 erreurs, 0 avertissements sur 55 fichiersnpx vitest run # 3/3 tests unitaires passésB. Validation Locale (Wrangler Dev - http://localhost:8787)
Section titled “B. Validation Locale (Wrangler Dev - http://localhost:8787)”- Connexion (
/api/auth/sign-in/email) :HTTP 200 OK, génération du cookieamn-auth.session_token. - Récupération de session (
/api/auth/get-session) :HTTP 200 OK, retour de la session et des données utilisateur.
C. Validation Distante (Cloudflare Worker amn-dev)
Section titled “C. Validation Distante (Cloudflare Worker amn-dev)”curl -i -X POST https://amn-dev.anderson-metsanou.workers.dev/api/auth/sign-in/email \ -H "Content-Type: application/json" \ -d '{"email":"andsinusmomega@gmail.com","password":"Debian13"}'Résultat : HTTP/2 200 OK
{ "redirect": false, "token": "9Ng3ypYV0f6Ix1tWSAU7sUl4wiOxcpsq", "user": { "id": "mWic8NK0CANSa22hxCHS5ZMdGuLZlYfS", "name": "omega1", "email": "andsinusmomega@gmail.com", "emailVerified": true, "role": "user" }}5. Recommandations et Bonnes Pratiques
Section titled “5. Recommandations et Bonnes Pratiques”- Toujours définir
storeSessionInDatabase: truelorsquesecondaryStorageest utilisé conjointement avec des plugins de gestion d’infrastructure (dash()). - Passer un schéma déstructuré à
drizzleAdapterplutôt qu’un import d’espace de nommage (import * as schema). - Spécifier les
modelNameexplicites dans la configurationbetterAuthlors de l’utilisation de tables au singulier (usePlural: false).