コードの標準と規約
LibreChatへの貢献に関するコーディング規約、ワークスペースの境界線、および慣習。
ワークスペースの境界
LibreChatはモノレポです。すべての新しいコードは、適切なワークスペースを対象とする必要があります:
| ワークスペース | 言語 | サイド | 目的 |
|---|---|---|---|
/api | JS (レガシー) | バックエンド | Expressサーバー — ここでの変更は最小限に抑えること |
/packages/api | TypeScript | バックエンド | 新しいバックエンドコードはここに配置 (TSのみ、/apiから利用) |
/packages/data-schemas | TypeScript | バックエンド | データベースモデル/スキーマおよびデータベース固有の共有ロジック |
/packages/data-provider | TypeScript | 共有 | API型、endpoint、データサービス — フロントエンドとバックエンドで使用 |
/client | TypeScript/React | フロントエンド | フロントエンドSPA |
/packages/client | TypeScript | フロントエンド | 共有フロントエンドユーティリティ |
/packages/api内のすべての新しいバックエンドコードは TypeScript で記述する必要があります。/apiへの変更は最小限に抑えてください(/packages/apiを呼び出す薄い JS ラッパーに留めること)。- データベース固有の共有ロジックは
/packages/data-schemasに配置します。 - フロントエンド/バックエンド間で共有されるAPIロジック(endpoints、types、data-service)は
/packages/data-providerに配置されます。 - プロジェクトのルートからすべてのコンパイル済みコードをビルドします:
npm run build。 - APIや型の変更後に共有データプロバイダーのコードを再ビルドするには、
npm run build:data-providerを実行します。
一般的なガイドライン
- 「クリーンコード」の原則に従ってください。関数やモジュールは小さく保ち、単一責任の原則を遵守し、表現力豊かで読みやすいコードを書いてください。
- 意味のある、説明的な変数名や関数名を使用してください。
- コードの簡潔さよりも、可読性と保守性を優先してください。
- 一貫したコードフォーマットを維持するために、提供されている
.eslintrcおよび.prettierrcファイルを使用してください。 - 利用可能な場合は自動修正を使用して、すべてのフォーマットのlintエラーを修正してください。すべてのTypeScript/ESLintの警告およびエラーは解決されている必要があります。
名前付けとファイル構成
- 可能な限り
permissions.ts、capabilities.ts、service.tsのような単一単語のファイル名を使用してください。 - 複数の単語が必要な場合は、
adminCapabilities.tsではなくadmin/capabilities.tsのように、ファイルの内容を示す単一単語のディレクトリを使用することを推奨します。 - ディレクトリがコンテキストを提供するようにしてください。
app/appConfigService.tsよりもapp/service.tsを優先します。
コード構造
- Never-nesting(ネストの禁止): アーリーリターンを使用し、コードをフラットに保ち、インデントを最小限に抑えます。複雑な操作は、適切な名前を付けたヘルパー関数に分割してください。
- 関数型ファースト: 命令型ループよりも純粋関数、不変データ、
map/filter/reduceを優先します。OOP(オブジェクト指向プログラミング)は、ドメインモデリングや状態のカプセル化を明確に改善する場合にのみ使用してください。 - どうしても必要な場合を除き、動的インポート(dynamic imports)は使用しないでください。
- 繰り返されるロジックを専用のユーティリティ関数に抽出してください(DRY原則)。重複に近い実装よりも、パラメータ化されたヘルパー、定数、共有バリデーター、一元化されたエラーハンドリング、および共有型を優先してください。
反復とパフォーマンス
- ループを最小限に抑える — 特にメッセージ配列のような共有データ構造に対するループは頻繁に行われるため注意が必要です。ループ処理が1回増えるごとに、大規模なスケールでは負荷が蓄積されます。
- 可能な限り、連続する O(n) 操作を単一のパスに統合してください。処理を組み合わせることができる場合は、同じコレクションに対して二度ループ処理を行わないようにしてください。
- 反復処理の必要性を減らすデータ構造を選択してください(例:
Array.find/Array.includesの代わりにルックアップ用のMap/Setを使用する)。 - 不要なオブジェクトの作成を避け、空間と時間のトレードオフを考慮してください。
- メモリリークの防止:クロージャの扱いに注意し、リソースやイベントリスナーを適切に破棄し、循環参照を避けてください。
型安全性
anyは絶対に使用しないでください。すべてのパラメータ、戻り値、および変数には明示的な型を指定してください。unknownの制限 —unknown、Record<string, unknown>、およびas unknown as Tアサーションの使用を避けてください。Record<string, unknown>は、ほとんどの場合、明示的な型定義が欠落していることを示しています。- 型を重複させない — 新しい型を定義する前に、プロジェクト内(特に
packages/data-provider)に既存の型が存在しないか確認してください。既存の型を再利用および拡張してください。 - ユニオン型、ジェネリクス、インターフェースを適切に使用してください。
コメントとドキュメント
- 自己文書化されたコードを書いてください。コードが何をしているかを説明するインラインコメントは不要です。
- 複雑なロジックや自明でないロジック、またはパブリックAPIのIntellisense用としてのみJSDocを使用してください。
- 簡潔なドキュメントには1行のJSDocを、複雑なケースには複数行のJSDocを使用してください。
- どうしても必要な場合を除き、スタンドアロンの
//コメントは避けてください。
インポート順序
インポートは(順に)3つのセクションに整理されています:
- パッケージのインポート — 行の長さが短い順から長い順にソートされています(
reactは常に最初のインポートとなります)。 import typeインポート — 長い順から短い順にソート(パッケージの型が先、次にローカルの型。長さによるソートはサブグループごとにリセットされます)。- ローカル/プロジェクトのインポート — 長い順から短い順にソート。
- 同じモジュールからの値のインポートは、可能な限り統合してください。
- 型インポートには常にスタンドアロンの
import type { ... }を使用してください。値インポート内でインラインのtypeキーワードを使用することは避けてください(例:import { Foo, type Bar }は誤りです)。
ループ設定
- ループ処理を可能な限り制限してください。 シングルパス(1回のみの走査)での変換を優先し、同じデータに対して繰り返し処理を行うことは避けてください。
- パフォーマンスが重要、またはインデックスに依存する操作には
for (let i = 0; ...)を使用します。 - 単純な配列の反復処理には
for...ofを使用します。 for...inはオブジェクトのプロパティ列挙のみに使用してください。
Node.js API Server
API 設計
- APIを設計する際は、RESTfulの原則に従ってください。
- ルート、コントローラー、サービス、およびモデルには、意味のある記述的な名前を使用してください。
- 各ルートに対して適切なHTTPメソッド(GET、POST、PUT、DELETE)を使用してください。
- 一貫性のあるAPIレスポンスのために、適切なステータスコードとレスポンス構造を使用してください(成功時は2xx、クライアントからの不正なリクエスト時は4xx、サーバーエラー時は5xx)。
- try-catchブロックを使用して、例外を適切にキャッチし処理してください。
- 適切なエラーハンドリングを実装し、一貫して適切なエラーレスポンスを返すようにしてください。
utilsディレクトリに含まれるロギングシステムを使用して、重要なイベントやエラーをログに記録してください。requireJWTAuthミドルウェアを使用して、JWTベースのステートレス認証を行います。
ファイル構造
新しいバックエンドコードは、TypeScriptとして /packages/api に配置されます。レガシーな /api ディレクトリは以下の構造に従います:
ルート
各HTTPリクエストメソッド、使用するミドルウェア、および各ルートに対して呼び出されるコントローラー関数を指定します。
- リソースごと、または論理的なグループごとに個別のファイルを作成し、Express Routerを使用してルートを定義します。
- 記述的なルート名を使用し、RESTfulな慣習に従ってください。
- ルートは簡潔に保ち、単一の責務に集中させてください。
- すべてのルートの先頭に
/api名前空間を付与してください。
Controllers
各ルートのロジックが含まれており、適切なサービス関数の呼び出しや、適切なレスポンスステータスコードおよびJSONボディの返却を行います。
- 各ルートに対して個別のコントローラーファイルを作成し、リクエスト/レスポンスのロジックを処理します。
- コントローラーファイルにはPascalCase命名規則を使用し、ファイル名の末尾に「Controller」を付加してください(例:
UserController.js)。 - 複雑な操作はサービスファイルやモデルファイルに委譲し、コントローラーを軽量に保ちます。
サービス
複数のコントローラー間で共有される複雑なビジネスロジックや操作が含まれています。
- サービスファイルの名前はPascalCase規則に従い、ファイル名の末尾に "Service" を付加してください(例:
AuthService.js)。 - 再利用性を高めるため、サービスを特定のモデルやデータベースに密結合させることは避けてください。
- 各サービス内で単一責任の原則を維持してください。
モデル
データエンティティとその関係性を表現するためのMongooseモデルを定義します。
- モデルファイルとその関連コレクションには、単数形のPascalCase名を使用してください(例:
User.jsおよびusersコレクション)。 - モデルには、必要なフィールド、インデックス、およびバリデーションのみを含めてください。
- リクエスト/レスポンスオブジェクトへの直接参照を避けることで、モデルをAPIレイヤーから独立させてください。
データベースアクセス (MongoDB and Mongoose)
- MongoDB ODMとしてMongoose (https://mongoosejs.com) を使用します。
- 各エンティティに対して個別のモデルファイルを作成し、関心の分離を明確にしてください。
- Mongooseスキーマバリデーションを使用して、データの整合性を強制します。
- データベース接続を効率的に処理し、接続リークを回避してください。
- Mongooseクエリビルダーを使用して、簡潔で読みやすいデータベースクエリを作成します。
React Client
一般的なTypeScriptとReactのベストプラクティス
- TypeScriptのベストプラクティスを活用して、静的型付けとツール機能の向上によるメリットを享受してください。
- 関連するファイルを機能ディレクトリ(例:
SidePanel/Memories/)内にまとめてグループ化します。 - コンポーネントにはPascalCase命名規則を使用してください。
- コンポーネントの目的を正確に反映した、簡潔で分かりやすい名前を使用してください。
- 適切な場合には、複雑なコンポーネントをより小さく再利用可能なコンポーネントに分割してください。
- コンポーネント内のレンダリングロジックは最小限に抑えてください。
- 再利用可能なパーツを個別の関数やフックに抽出します。
- TypeScriptの型またはインターフェースを使用して、propの型定義を適用します。
- 適切な場所でフォームバリデーションを使用してください(フォームのバリデーションと送信には React Hook Form を使用しています)。
ローカライズ
- クライアントに表示されるすべてのテキストは、
useLocalize()フックを使用してローカライズする必要があります。 client/src/locales/en/translation.json内の英語のキーのみを更新してください(他の言語は外部で自動化されています)。- セマンティックなローカライゼーションキーのプレフィックス(
com_ui_、com_assistants_など)を使用してください。 - 新しいローカライゼーションキーには、常に意味のあるフォールバックテキストを提供してください。
データサービス
client/src/data-provider/[Feature]/queries.tsにデータプロバイダーのフックを作成します。client/src/data-provider/[Feature]/index.tsからすべてのフックをエクスポートします。- メインの
client/src/data-provider/index.tsに機能のエクスポートを追加します。 - すべてのAPI操作にはReact Query (
@tanstack/react-query) を使用してください。 - ミューテーション時に適切なクエリ無効化を実装してください。
packages/data-provider/src/keys.tsに QueryKeys と MutationKeys を追加します。
共有API統合を追加する際は、以下を更新してください:
packages/data-provider/src/api-endpoints.ts(endpoint)packages/data-provider/src/data-service.ts(データサービス関数)packages/data-provider/src/types/queries.ts(TypeScript 型)
パフォーマンス
- 大規模な環境において、メモリと速度の効率を優先します。
- 大規模なデータセットに対して適切なカーソルページネーションを実装してください。
- 適切な依存配列を使用して、不要な再レンダリングを回避してください。
- React Queryのキャッシュおよびバックグラウンド再フェッチ機能を活用します。
テストとドキュメント
- Jestを使用して、すべての重要かつ複雑な機能に対するユニットテストを作成してください。
- Supertestを使用して、すべてのAPI endpointの統合テストを作成してください。
- Playwrightを使用して、すべてのクライアントサイド機能のエンドツーエンドテストを作成してください。
- テストの目的を明確にするため、記述的なテストケース名と関数名を使用してください。
- ワークスペースディレクトリからテストを実行します:
cd api && npx jest <pattern>、cd packages/api && npx jest <pattern>など。 - UI/データフローの読み込み中、成功、およびエラー状態をカバーします。
- フロントエンドテストでコンポーネントをレンダリングするには、
test/layout-test-utilsを使用してください。 - モックよりも実際のロジックを優先してください。モックは、外部HTTP API、レート制限のあるサービス、非決定的なシステムコールなど、ローカルで制御できないものに対してのみ使用してください。
- 基盤となる実装を置き換えることなく呼び出しをアサートする必要がある場合は、スパイを使用してください。
- MongoDBベースのテストには
mongodb-memory-serverを使用し、クエリとスキーマのバリデーションが実際のデータベースの動作を検証できるようにします。
このガイドはいかがでしたか?