Normes et conventions de code
Normes de codage, limites de l'espace de travail et conventions pour contribuer à LibreChat.
Limites de l'espace de travail
LibreChat est un monorepo. Tout nouveau code doit cibler l'espace de travail approprié :
| Espace de travail | Langage | Côté | Objectif |
|---|---|---|---|
/api | JS (hérité) | Backend | Serveur Express — minimiser les changements ici |
/packages/api | TypeScript | Backend | Le nouveau code backend réside ici (TS uniquement, consommé par /api) |
/packages/data-schemas | TypeScript | Backend | Modèles/schémas de base de données et logique partagée spécifique à la base de données |
/packages/data-provider | TypeScript | Partagé | Types API, endpoint, service de données — utilisé par le frontend et le backend |
/client | TypeScript/React | Frontend | SPA frontend |
/packages/client | TypeScript | Frontend | Utilitaires frontend partagés |
- Tout nouveau code backend doit être en TypeScript dans
/packages/api. - Gardez les modifications de
/apiau strict minimum (des wrappers JS légers appelant/packages/api). - La logique partagée spécifique à la base de données se trouve dans
/packages/data-schemas. - La logique API partagée entre le frontend et le backend (endpoints, types, data-service) se trouve dans
/packages/data-provider. - Compilez tout le code du projet depuis la racine :
npm run build. - Reconstruisez le code du fournisseur de données partagé après des changements d'API/type :
npm run build:data-provider.
Directives générales
- Utilisez les principes du « clean code » : gardez les fonctions et les modules petits, respectez le principe de responsabilité unique et écrivez du code expressif et lisible.
- Utilisez des noms de variables et de fonctions significatifs et descriptifs.
- Privilégiez la lisibilité et la maintenabilité du code plutôt que la brièveté.
- Utilisez les fichiers
.eslintrcet.prettierrcfournis pour un formatage de code cohérent. - Corrigez toutes les erreurs de formatage du lint en utilisant la correction automatique lorsque disponible. Tous les avertissements et erreurs TypeScript/ESLint doivent être résolus.
Nommage et organisation des fichiers
- Utilisez des noms de fichiers en un seul mot dans la mesure du possible, tels que
permissions.ts,capabilities.tsouservice.ts. - Lorsque plusieurs mots sont nécessaires, préférez un répertoire en un seul mot qui donne du contexte au fichier, tel que
admin/capabilities.tsau lieu deadminCapabilities.ts. - Laissez le répertoire fournir le contexte. Préférez
app/service.tsàapp/appConfigService.ts.
Structure du code
- Ne jamais imbriquer : utilisez des retours anticipés (early returns), un code plat et une indentation minimale. Divisez les opérations complexes en fonctions d'assistance bien nommées.
- Priorité au fonctionnel : fonctions pures, données immuables,
map/filter/reduceplutôt que des boucles impératives. Ne recourir à la POO que lorsque cela améliore clairement la modélisation du domaine ou l'encapsulation de l'état. - Pas d'importations dynamiques sauf si absolument nécessaire.
- Extrayez la logique répétée dans des fonctions utilitaires dédiées (DRY). Privilégiez les assistants paramétrés, les constantes, les validateurs partagés, la gestion centralisée des erreurs et les types partagés plutôt que des implémentations quasi identiques.
Itération et performance
- Minimisez les boucles — en particulier sur les structures de données partagées comme les tableaux de messages, qui sont fréquemment itérés. Chaque passage supplémentaire s'accumule à grande échelle.
- Consolidez les opérations séquentielles O(n) en un seul passage dans la mesure du possible ; ne bouclez jamais deux fois sur la même collection si le travail peut être combiné.
- Choisissez des structures de données qui réduisent le besoin d'itérer (par exemple,
Map/Setpour les recherches au lieu deArray.find/Array.includes). - Évitez la création d'objets inutile ; considérez les compromis espace-temps.
- Prévenir les fuites de mémoire : soyez prudent avec les fermetures (closures), libérez les ressources/écouteurs d'événements, et évitez les références circulaires.
Sécurité de type
- Ne jamais utiliser
any. Types explicites pour tous les paramètres, valeurs de retour et variables. - Limiter
unknown— évitezunknown,Record<string, unknown>et les assertionsas unknown as T. UnRecord<string, unknown>signale presque toujours une définition de type explicite manquante. - Ne dupliquez pas les types — vérifiez si un type existe déjà dans le projet (en particulier dans
packages/data-provider) avant d'en définir un nouveau. Réutilisez et étendez les types existants. - Utilisez les types union, les génériques et les interfaces de manière appropriée.
Commentaires et documentation
- Écrivez du code auto-documenté ; pas de commentaires en ligne racontant ce que fait le code.
- JSDoc uniquement pour la logique complexe/non évidente ou pour l'IntelliSense sur les API publiques.
- JSDoc sur une seule ligne pour une documentation brève, sur plusieurs lignes pour les cas complexes.
- Évitez les commentaires
//isolés, sauf si cela est absolument nécessaire.
Ordre d'importation
Les imports sont organisés en trois sections (dans l'ordre) :
- Importations de paquets — triées de la plus courte à la plus longue longueur de ligne (
reactest toujours la première importation). import typeimports — triés du plus long au plus court (types de paquets d'abord, puis types locaux ; le tri par longueur est réinitialisé entre les sous-groupes).- Importations locales/de projet — triées de la plus longue à la plus courte.
- Consolidez autant que possible les importations de valeurs provenant du même module.
- Utilisez toujours
import type { ... }autonome pour les importations de types ; n'utilisez jamais le mot-clétypeen ligne à l'intérieur des importations de valeurs (par exemple,import { Foo, type Bar }est incorrect).
Préférences de boucle
- Limitez autant que possible les boucles. Privilégiez les transformations en un seul passage et évitez de parcourir plusieurs fois les mêmes données.
for (let i = 0; ...)pour les opérations critiques en termes de performance ou dépendantes de l'index.for...ofpour une itération simple sur un tableau.for...inuniquement pour l'énumération des propriétés d'un objet.
Serveur API Node.js
Conception de l'API
- Suivez les principes RESTful lors de la conception d'APIs.
- Utilisez des noms significatifs et descriptifs pour les routes, les contrôleurs, les services et les modèles.
- Utilisez les méthodes HTTP appropriées (GET, POST, PUT, DELETE) pour chaque route.
- Utilisez des codes de statut et des structures de réponse appropriés pour des réponses API cohérentes (2xx pour le succès, 4xx pour une mauvaise requête du client, 5xx pour une erreur serveur).
- Utilisez des blocs try-catch pour intercepter et gérer les exceptions de manière élégante.
- Implémentez une gestion des erreurs appropriée et renvoyez systématiquement des réponses d'erreur adaptées.
- Utilisez le système de journalisation inclus dans le répertoire
utilspour consigner les événements importants et les erreurs. - Effectuez une authentification sans état basée sur JWT en utilisant le middleware
requireJWTAuth.
Structure des fichiers
Le nouveau code backend se trouve dans /packages/api en TypeScript. Le répertoire hérité /api suit cette structure :
Routes
Spécifie chaque méthode de requête HTTP, tout middleware à utiliser, ainsi que la fonction de contrôleur à appeler pour chaque route.
- Définissez les routes en utilisant l'Express Router dans des fichiers séparés pour chaque ressource ou regroupement logique.
- Utilisez des noms de route descriptifs et respectez les conventions RESTful.
- Gardez les routes concises et concentrées sur une responsabilité unique.
- Préfixez toutes les routes avec l'espace de noms
/api.
Contrôleurs
Contient la logique pour chaque route, y compris l'appel des fonctions de service appropriées et le renvoi du code d'état de réponse et du corps JSON appropriés.
- Créez un fichier de contrôleur distinct pour chaque route afin de gérer la logique de requête/réponse.
- Nommez les fichiers de contrôleur en utilisant la convention PascalCase et ajoutez "Controller" à la fin du nom de fichier (par exemple,
UserController.js). - Gardez les contrôleurs légers en déléguant les opérations complexes aux fichiers de service ou de modèle.
Services
Contient une logique métier complexe ou des opérations partagées entre plusieurs contrôleurs.
- Nommez les fichiers de service en utilisant la convention PascalCase et ajoutez "Service" à la fin du nom de fichier (par exemple,
AuthService.js). - Évitez de coupler étroitement les services à des modèles ou des bases de données spécifiques pour une meilleure réutilisabilité.
- Maintenez un principe de responsabilité unique au sein de chaque service.
Modèles
Définit les modèles Mongoose pour représenter les entités de données et leurs relations.
- Utilisez des noms au singulier en PascalCase pour les fichiers de modèle et leurs collections associées (par exemple,
User.jset la collectionusers). - Incluez uniquement les champs, index et validations nécessaires dans les modèles.
- Gardez les modèles indépendants de la couche API en évitant les références directes aux objets de requête/réponse.
Accès à la base de données (MongoDB et Mongoose)
- Utilisez Mongoose (https://mongoosejs.com) comme ODM MongoDB.
- Créez des fichiers de modèle distincts pour chaque entité et assurez une séparation claire des préoccupations.
- Utilisez la validation de schéma Mongoose pour garantir l'intégrité des données.
- Gérez efficacement les connexions à la base de données et évitez les fuites de connexion.
- Utilisez les constructeurs de requêtes Mongoose pour créer des requêtes de base de données concises et lisibles.
Client React
Meilleures pratiques générales pour TypeScript et React
- Utilisez les meilleures pratiques TypeScript pour bénéficier du typage statique et d'outils améliorés.
- Regroupez les fichiers associés au sein de répertoires de fonctionnalités (par exemple,
SidePanel/Memories/). - Nommez les composants en utilisant la convention PascalCase.
- Utilisez des noms concis et descriptifs qui reflètent fidèlement l'objectif du composant.
- Divisez les composants complexes en composants plus petits et réutilisables lorsque cela est approprié.
- Gardez la logique de rendu au sein des composants aussi minimale que possible.
- Extrayez les parties réutilisables dans des fonctions ou des hooks séparés.
- Appliquez les définitions de type des props en utilisant des types ou des interfaces TypeScript.
- Utilisez la validation de formulaire là où c'est approprié (nous utilisons React Hook Form pour la validation et la soumission des formulaires).
Localisation
- Tout texte destiné à l'utilisateur final doit être localisé en utilisant le hook
useLocalize(). - Mettez uniquement à jour les clés anglaises dans
client/src/locales/en/translation.json(les autres langues sont automatisées en externe). - Utilisez des préfixes de clés de localisation sémantiques :
com_ui_,com_assistants_, etc. - Fournissez toujours un texte de secours significatif pour les nouvelles clés de localisation.
Services de données
- Créez des hooks de fournisseur de données dans
client/src/data-provider/[Feature]/queries.ts. - Exportez tous les hooks depuis
client/src/data-provider/[Feature]/index.ts. - Ajoutez les exports de fonctionnalités dans le fichier principal
client/src/data-provider/index.ts. - Utilisez React Query (
@tanstack/react-query) pour toutes les interactions avec l'API. - Implémenter une invalidation de requête appropriée sur les mutations.
- Ajoutez QueryKeys et MutationKeys à
packages/data-provider/src/keys.ts.
Lors de l'ajout d'une intégration d'API partagée, mettez à jour :
packages/data-provider/src/api-endpoints.ts(endpoints)packages/data-provider/src/data-service.ts(fonctions du service de données)packages/data-provider/src/types/queries.ts(Types TypeScript)
Performance
- Priorisez l'efficacité de la mémoire et de la vitesse à grande échelle.
- Implémenter une pagination par curseur appropriée pour les grands ensembles de données.
- Évitez les re-rendus inutiles avec des tableaux de dépendances appropriés.
- Tirez parti des fonctionnalités de mise en cache et de réactualisation en arrière-plan de React Query.
Tests et documentation
- Écrivez des tests unitaires pour toutes les fonctionnalités critiques et complexes en utilisant Jest.
- Écrire des tests d'intégration pour tous les endpoint de l'API en utilisant Supertest.
- Écrivez des tests de bout en bout pour toutes les fonctionnalités côté client en utilisant Playwright.
- Utilisez des noms de cas de test et de fonctions descriptifs pour exprimer clairement l'objectif du test.
- Exécutez les tests depuis leur répertoire de travail :
cd api && npx jest <pattern>,cd packages/api && npx jest <pattern>, etc. - Couvrir les états de chargement, de succès et d'erreur pour les flux d'interface utilisateur/données.
- Utilisez
test/layout-test-utilspour le rendu des composants dans les tests frontend. - Privilégiez la logique réelle aux mocks. Ne simulez que ce qui ne peut pas être contrôlé localement, comme les API HTTP externes, les services limités en débit et les appels système non déterministes.
- Utilisez des espions (spies) lorsque vous avez besoin de vérifier des appels sans remplacer l'implémentation sous-jacente.
- Utilisez
mongodb-memory-serverpour les tests basés sur MongoDB afin que les requêtes et la validation de schéma exercent un comportement de base de données réel.
Que pensez-vous de ce guide ?