Standardy i konwencje kodu
Standardy kodowania, granice obszaru roboczego oraz konwencje dotyczące współtworzenia LibreChat.
Granice obszaru roboczego
LibreChat to monorepo. Cały nowy kod powinien być kierowany do odpowiedniego obszaru roboczego (workspace):
| Obszar roboczy | Język | Strona | Przeznaczenie |
|---|---|---|---|
/api | JS (legacy) | Backend | Serwer Express — ogranicz zmiany w tym miejscu |
/packages/api | TypeScript | Backend | Nowy kod backendowy znajduje się tutaj (tylko TS, używany przez /api) |
/packages/data-schemas | TypeScript | Backend | Modele/schematy bazy danych oraz współdzielona logika specyficzna dla bazy danych |
/packages/data-provider | TypeScript | Współdzielone | Typy API, endpoint, data-service — używane przez frontend i backend |
/client | TypeScript/React | Frontend | Frontend SPA |
/packages/client | TypeScript | Frontend | Współdzielone narzędzia frontendowe |
- Cały nowy kod backendowy musi być w TypeScript w
/packages/api. - Ogranicz zmiany w
/apido absolutnego minimum (lekkie wrappery JS wywołujące/packages/api). - Logika współdzielona specyficzna dla bazy danych znajduje się w
/packages/data-schemas. - Wspólna logika API dla frontend/backend (endpointy, typy, data-service) znajduje się w
/packages/data-provider. - Zbuduj cały skompilowany kod z katalogu głównego projektu:
npm run build. - Przebuduj współdzielony kod data-provider po zmianach w API/typach:
npm run build:data-provider.
Ogólne wytyczne
- Stosuj zasady "clean code": dbaj o to, aby funkcje i moduły były małe, przestrzegaj zasady pojedynczej odpowiedzialności oraz pisz kod wyrazisty i czytelny.
- Używaj znaczących i opisowych nazw zmiennych oraz funkcji.
- Przedkładaj czytelność i łatwość utrzymania kodu nad jego zwięzłość.
- Użyj dostarczonych plików
.eslintrcoraz.prettierrcw celu zachowania spójnego formatowania kodu. - Napraw wszystkie błędy formatowania lint przy użyciu automatycznej naprawy, jeśli jest dostępna. Wszystkie ostrzeżenia i błędy TypeScript/ESLint muszą zostać rozwiązane.
Naming and File Organization
- Używaj jednowyrazowych nazw plików, kiedy tylko to możliwe, takich jak
permissions.ts,capabilities.tslubservice.ts. - Jeśli potrzebnych jest wiele słów, preferuj katalog jednowyrazowy, który nadaje plikowi kontekst, na przykład
admin/capabilities.tszamiastadminCapabilities.ts. - Niech katalog zapewnia kontekst. Preferuj
app/service.tszamiastapp/appConfigService.ts.
Struktura kodu
- Unikanie zagnieżdżeń: stosuj wczesne powroty (early returns), płaski kod i minimalne wcięcia. Dziel złożone operacje na dobrze nazwane funkcje pomocnicze.
- Funkcjonalność przede wszystkim: czyste funkcje, niezmienne dane,
map/filter/reducezamiast imperatywnych pętli. Sięgaj po OOP tylko wtedy, gdy wyraźnie poprawia to modelowanie domeny lub enkapsulację stanu. - Brak dynamicznych importów, chyba że jest to absolutnie konieczne.
- Wydziel powtarzającą się logikę do dedykowanych funkcji narzędziowych (DRY). Preferuj sparametryzowane pomocniki, stałe, współdzielone walidatory, scentralizowaną obsługę błędów oraz współdzielone typy zamiast implementacji niemal identycznych.
Iteracja i wydajność
- Minimalizuj pętle — zwłaszcza w przypadku współdzielonych struktur danych, takich jak tablice wiadomości, które są często iterowane. Każde dodatkowe przejście sumuje się przy większej skali.
- Konsoliduj sekwencyjne operacje O(n) w pojedyncze przejście, kiedy tylko to możliwe; nigdy nie iteruj po tej samej kolekcji dwukrotnie, jeśli pracę można połączyć.
- Wybieraj struktury danych, które ograniczają potrzebę iteracji (np.
Map/Setdo wyszukiwania zamiastArray.find/Array.includes). - Unikaj niepotrzebnego tworzenia obiektów; rozważ kompromisy między czasem a przestrzenią.
- Zapobieganie wyciekom pamięci: zachowaj ostrożność przy domknięciach (closures), zwalniaj zasoby/detektory zdarzeń (event listeners), unikaj odwołań cyklicznych.
Bezpieczeństwo typów
- Nigdy nie używaj
any. Wymagane są jawne typy dla wszystkich parametrów, wartości zwracanych oraz zmiennych. - Ogranicz
unknown— unikajunknown,Record<string, unknown>oraz asercjias unknown as T.Record<string, unknown>prawie zawsze sygnalizuje brak jawnej definicji typu. - Nie duplikuj typów — sprawdź, czy dany typ już istnieje w projekcie (zwłaszcza w
packages/data-provider), zanim zdefiniujesz nowy. Używaj ponownie i rozszerzaj istniejące typy. - Używaj odpowiednio typów unii, typów generycznych oraz interfejsów.
Komentarze i dokumentacja
- Pisz kod samokomentujący; nie używaj komentarzy wewnątrz kodu opisujących, co dany kod robi.
- JSDoc tylko dla złożonej/nieoczywistej logiki lub dla funkcji intellisense w publicznych API.
- Jednoliniowy JSDoc dla krótkiej dokumentacji, wieloliniowy dla złożonych przypadków.
- Unikaj samodzielnych komentarzy
//, chyba że jest to absolutnie konieczne.
Kolejność importu
Importy są uporządkowane w trzech sekcjach (w podanej kolejności):
- Importy pakietów — posortowane od najkrótszej do najdłuższej długości linii (
reactjest zawsze pierwszym importem). import typeimports — posortowane od najdłuższego do najkrótszego (najpierw typy pakietów, następnie typy lokalne; sortowanie według długości resetuje się pomiędzy podgrupami).- Importy lokalne/projektowe — posortowane od najdłuższych do najkrótszych.
- W miarę możliwości konsoliduj importy wartości z tego samego modułu.
- Zawsze używaj samodzielnego
import type { ... }dla importów typów; nigdy nie używaj słowa kluczowegotypewewnątrz importów wartości (np.import { Foo, type Bar }jest błędne).
Preferencje pętli
- Ograniczaj pętle w największym możliwym stopniu. Preferuj transformacje jednoprzebiegowe i unikaj wielokrotnego iterowania po tych samych danych.
for (let i = 0; ...)dla operacji krytycznych pod względem wydajności lub zależnych od indeksu.for...ofdo prostej iteracji po tablicy.for...intylko do wyliczania właściwości obiektu.
Serwer API Node.js
Projekt API
- Podczas projektowania API należy przestrzegać zasad RESTful.
- Używaj znaczących i opisowych nazw dla tras (routes), kontrolerów, serwisów i modeli.
- Używaj odpowiednich metod HTTP (GET, POST, PUT, DELETE) dla każdej trasy.
- Używaj odpowiednich kodów statusu i struktur odpowiedzi, aby zapewnić spójność odpowiedzi API (2xx dla sukcesu, 4xx dla błędnego żądania klienta, 5xx dla błędu serwera).
- Używaj bloków try-catch, aby przechwytywać i obsługiwać wyjątki w sposób bezpieczny.
- Wdróż odpowiednią obsługę błędów i konsekwentnie zwracaj właściwe odpowiedzi o błędach.
- Użyj systemu logowania zawartego w katalogu
utils, aby rejestrować ważne zdarzenia i błędy. - Użyj uwierzytelniania bezstanowego opartego na JWT za pomocą middleware
requireJWTAuth.
Struktura plików
Nowy kod backendu znajduje się w /packages/api jako TypeScript. Starszy katalog /api zachowuje tę strukturę:
Trasy
Określa każdą metodę żądania HTTP, wszelkie używane oprogramowanie pośredniczące (middleware) oraz funkcję kontrolera, która ma zostać wywołana dla każdej trasy.
- Definiuj trasy przy użyciu Express Router w oddzielnych plikach dla każdego zasobu lub logicznego grupowania.
- Używaj opisowych nazw tras i przestrzegaj konwencji RESTful.
- Utrzymuj trasy zwięzłe i skoncentrowane na pojedynczej odpowiedzialności.
- Dodaj prefiks
/apido wszystkich tras.
Kontrolery
Zawiera logikę dla każdej trasy, w tym wywoływanie odpowiednich funkcji serwisowych oraz zwracanie właściwego kodu statusu odpowiedzi i treści JSON.
- Utwórz oddzielny plik kontrolera dla każdej trasy, aby obsługiwać logikę żądania/odpowiedzi.
- Pliki kontrolerów należy nazywać zgodnie z konwencją PascalCase, dodając przyrostek "Controller" do nazwy pliku (np.
UserController.js). - Utrzymuj kontrolery w formie „chudej” (thin), delegując złożone operacje do plików serwisów lub modeli.
Usługi
Zawiera złożoną logikę biznesową lub operacje współdzielone przez wiele kontrolerów.
- Nazywaj pliki serwisów zgodnie z konwencją PascalCase i dodawaj przyrostek "Service" do nazwy pliku (np.
AuthService.js). - Unikaj ścisłego powiązania usług z konkretnymi modelami lub bazami danych, aby zwiększyć możliwość ich ponownego wykorzystania.
- Utrzymuj zasadę jednej odpowiedzialności w ramach każdej usługi.
Modele
Definiuje modele Mongoose reprezentujące encje danych i ich relacje.
- Używaj nazw w liczbie pojedynczej i formacie PascalCase dla plików modeli oraz powiązanych z nimi kolekcji (np.
User.jsi kolekcjausers). - Uwzględnij w modelach tylko niezbędne pola, indeksy i walidacje.
- Utrzymuj modele niezależne od warstwy API, unikając bezpośrednich odwołań do obiektów żądań/odpowiedzi (request/response).
Dostęp do bazy danych (MongoDB i Mongoose)
- Użyj Mongoose (https://mongoosejs.com) jako ODM dla MongoDB.
- Utwórz oddzielne pliki modeli dla każdej encji i zapewnij wyraźny podział odpowiedzialności.
- Użyj walidacji schematu Mongoose, aby wymusić integralność danych.
- Efektywnie zarządzaj połączeniami z bazą danych i unikaj wycieków połączeń.
- Używaj konstruktorów zapytań Mongoose, aby tworzyć zwięzłe i czytelne zapytania do bazy danych.
Klient React
Ogólne najlepsze praktyki TypeScript i React
- Stosuj dobre praktyki TypeScript, aby korzystać ze statycznego typowania i ulepszonych narzędzi.
- Grupuj powiązane pliki wewnątrz katalogów funkcji (np.
SidePanel/Memories/). - Nazywaj komponenty, używając konwencji PascalCase.
- Używaj zwięzłych i opisowych nazw, które dokładnie odzwierciedlają przeznaczenie komponentu.
- Dziel złożone komponenty na mniejsze, możliwe do ponownego wykorzystania, gdy jest to stosowne.
- Utrzymuj logikę renderowania wewnątrz komponentów na minimalnym poziomie.
- Wydziel powtarzalne części do oddzielnych funkcji lub hooków.
- Zastosuj definicje typów właściwości (props), używając typów lub interfejsów TypeScript.
- Używaj walidacji formularzy tam, gdzie jest to stosowne (do walidacji i przesyłania formularzy używamy React Hook Form).
Lokalizacja
- Wszystkie teksty widoczne dla klienta muszą być zlokalizowane przy użyciu hooka
useLocalize(). - Aktualizuj tylko angielskie klucze w
client/src/locales/en/translation.json(pozostałe języki są automatyzowane zewnętrznie). - Używaj prefiksów kluczy lokalizacji semantycznej:
com_ui_,com_assistants_itp. - Zawsze podawaj znaczący tekst zastępczy (fallback) dla nowych kluczy lokalizacji.
Usługi danych
- Utwórz hooki dostawcy danych w
client/src/data-provider/[Feature]/queries.ts. - Eksportuj wszystkie hooki z
client/src/data-provider/[Feature]/index.ts. - Dodaj eksporty funkcji do głównego
client/src/data-provider/index.ts. - Używaj React Query (
@tanstack/react-query) do wszystkich interakcji z API. - Wdróż odpowiednią unieważnienie zapytań (query invalidation) przy mutacjach.
- Dodaj QueryKeys i MutationKeys do
packages/data-provider/src/keys.ts.
Podczas dodawania współdzielonej integracji API, zaktualizuj:
packages/data-provider/src/api-endpoints.ts(punkty końcowe)packages/data-provider/src/data-service.ts(funkcje serwisu danych)packages/data-provider/src/types/queries.ts(typy TypeScript)
Wydajność
- Priorytetyzuj wydajność pamięci i szybkość działania na dużą skalę.
- Wdróż odpowiednią paginację kursorową dla dużych zbiorów danych.
- Unikaj niepotrzebnego ponownego renderowania dzięki odpowiednim tablicom zależności.
- Wykorzystaj funkcje buforowania i odświeżania w tle (background refetching) biblioteki React Query.
Testowanie i dokumentacja
- Napisz testy jednostkowe dla wszystkich krytycznych i złożonych funkcjonalności przy użyciu Jest.
- Napisz testy integracyjne dla wszystkich endpointów API przy użyciu Supertest.
- Napisz testy end-to-end dla wszystkich funkcjonalności po stronie klienta przy użyciu Playwright.
- Używaj opisowych nazw przypadków testowych i funkcji, aby jasno wyrazić cel testu.
- Uruchom testy z ich katalogu roboczego:
cd api && npx jest <pattern>,cd packages/api && npx jest <pattern>itd. - Obsługa stanów ładowania, powodzenia i błędów dla przepływów interfejsu użytkownika/danych.
- Użyj
test/layout-test-utilsdo renderowania komponentów w testach frontendowych. - Preferuj rzeczywistą logikę zamiast mocków. Mockuj tylko to, czego nie można kontrolować lokalnie, takie jak zewnętrzne API HTTP, usługi z ograniczeniami liczby zapytań (rate-limited) oraz niedeterministyczne wywołania systemowe.
- Używaj spies, gdy musisz zweryfikować wywołania bez zastępowania bazowej implementacji.
- Użyj
mongodb-memory-serverdo testów opartych na MongoDB, aby zapytania i walidacja schematu odzwierciedlały rzeczywiste zachowanie bazy danych.
Jaka jest ta instrukcja?