Skip to main content
LibreChat is joining ClickHouse to power the open-source Agentic Data Stack 🎉 Learn more
LibreChat

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 roboczyJęzykStronaPrzeznaczenie
/apiJS (legacy)BackendSerwer Express — ogranicz zmiany w tym miejscu
/packages/apiTypeScriptBackendNowy kod backendowy znajduje się tutaj (tylko TS, używany przez /api)
/packages/data-schemasTypeScriptBackendModele/schematy bazy danych oraz współdzielona logika specyficzna dla bazy danych
/packages/data-providerTypeScriptWspółdzieloneTypy API, endpoint, data-service — używane przez frontend i backend
/clientTypeScript/ReactFrontendFrontend SPA
/packages/clientTypeScriptFrontendWspółdzielone narzędzia frontendowe
  • Cały nowy kod backendowy musi być w TypeScript w /packages/api.
  • Ogranicz zmiany w /api do 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 .eslintrc oraz .prettierrc w 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.ts lub service.ts.
  • Jeśli potrzebnych jest wiele słów, preferuj katalog jednowyrazowy, który nadaje plikowi kontekst, na przykład admin/capabilities.ts zamiast adminCapabilities.ts.
  • Niech katalog zapewnia kontekst. Preferuj app/service.ts zamiast app/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/reduce zamiast 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/Set do wyszukiwania zamiast Array.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 — unikaj unknown, Record<string, unknown> oraz asercji as 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):

  1. Importy pakietów — posortowane od najkrótszej do najdłuższej długości linii (react jest zawsze pierwszym importem).
  2. import type imports — 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).
  3. 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 kluczowego type wewną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...of do prostej iteracji po tablicy.
  • for...in tylko 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 /api do 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.js i kolekcja users).
  • 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-utils do 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-server do testów opartych na MongoDB, aby zapytania i walidacja schematu odzwierciedlały rzeczywiste zachowanie bazy danych.

Jaka jest ta instrukcja?