LibreChatはChatGPT互換のOSSチャットUIで、実際に多くのユーザーが使っています。今回のsubmit.tsは「チャット入力欄からの送信」を1箇所に集約する小さなファイルです。UIの状態(回答待ち中か、AIが応答を生成中か等)によって「送信」の意味が変わるという、実務でよくある分岐処理を、型とearly returnだけで綺麗に書いている点が学びになると思い選びました。

コード

export type ComposerSubmitRoute = {
  answerMode: { active: boolean; submitText: (text: string) => boolean };
  steering: { duringRunActive: boolean; submitDuringRun: (text: string) => boolean };
  submitMessage: (data: { text: string }) => false | void;
  reset: () => void;
};

/**
 * One route for every submission that originates in the composer, whether
 * typed, dictated, or bound to a shortcut. Answer mode takes the text for the
 * paused run; during a run, or while a queued follow-up is about to start, the
 * text steers or queues per the effective action instead of starting a turn
 * that would race the one already owed. `false` means the composer keeps the
 * text, exactly as a refused send does.
 */
export function submitFromComposer(
  route: ComposerSubmitRoute,
  data: { text: string },
): false | void {
  if (route.answerMode.active && route.answerMode.submitText(data.text)) {
    return;
  }
  if (route.steering.duringRunActive) {
    if (!route.steering.submitDuringRun(data.text)) {
      return false;
    }
    route.reset();
    return;
  }
  return route.submitMessage(data);
}

引用はリポジトリの実物と機械で照合しています。「// …略…」は省略した行です。

上から順に読む

export type ComposerSubmitRoute = {

`type 名前 = { ... }` はTypeScript独自の構文で「型エイリアス」と呼びます。オブジェクトの形(どんなプロパティを持つか)に名前を付けているだけで、実行時には何も残らない、コンパイル時だけの約束事です。`export`が付いているので他のファイルからもこの型を使えます。ここでは「送信という操作に必要な材料一式」を先に型として定義しています。

answerMode: { active: boolean; submitText: (text: string) => boolean };

オブジェクト型の中にさらにオブジェクト型を書けます(ネスト)。`active: boolean`は「true/falseの値を持つプロパティ」。`submitText: (text: string) => boolean`は関数の型で、「文字列を1つ受け取ってbooleanを返す関数」という意味です。つまりanswerModeというプロパティの中には、状態を表す`active`と、実際に処理を行う関数`submitText`がセットで入っている、ということをこの1行だけで読み取れます。

submitMessage: (data: { text: string }) => false | void;

`false | void`は「ユニオン型」で、`|`は「またはどちらか」を意味します。`void`は「戻り値が実質ない(undefinedが返る)関数」を表す型で、Goに慣れているなら「何も返さない関数」のイメージに近いです。ここでは`false`という特定の値か、何も返さないかのどちらかがあり得る、という戻り値の仕様を型で明示しています。後で出てくる`return false`と`return;`(何も値を書かないreturn)がこの型に対応します。

export function submitFromComposer(
  route: ComposerSubmitRoute,
  data: { text: string },
): false | void {

関数定義です。引数`route`には先ほど定義した`ComposerSubmitRoute`型を指定しているので、`route.answerMode.submitText`のように呼び出しても、必要なプロパティが揃っていることをTypeScriptがコンパイル時にチェックしてくれます。関数全体の戻り値も`false | void`で、呼び出し側は「falseが返ってきたら特別な意味がある」と読み取れる契約になっています。

if (route.answerMode.active && route.answerMode.submitText(data.text)) {
    return;
  }

`&&`は論理AND演算子で、左側が`false`なら右側は評価されません(短絡評価)。つまり`route.answerMode.active`が`false`のときは`submitText`は一切呼ばれず、次のif文に進みます。`active`が`true`のときだけ実際に送信を試み、その結果(true/false)が全体の条件になります。両方成立したら`return;`(値を書かないreturn=void)で関数を抜け、以降の処理は実行されません。これは「早期リターン(early return)」と呼ばれるパターンで、ネストの深いif-elseを避けるためによく使われます。

if (route.steering.duringRunActive) {

1つ目のif文を通過した(=回答待ち中ではなかった)場合にここへ来ます。AIが応答を生成している最中(`duringRunActive`がtrue)かどうかで、送信の扱いを変える分岐です。

if (!route.steering.submitDuringRun(data.text)) {
      return false;
    }

`!`は論理否定(NOT)演算子で、boolean値を反転させます。`submitDuringRun(data.text)`を呼び出し、その戻り値が`false`だった場合(`!false`で条件が`true`になる)に、この関数自体も`false`を返して終了します。JSDocコメントにある通り、`false`は「入力欄のテキストを消さずに残す」という合図として使われており、送信操作が実は失敗(拒否)したことを呼び出し元に伝えています。

route.reset();
    return;

内側のif文を通過した(=ステアリング処理が成功した)場合、`route.reset()`という関数を呼んでから、値を書かない`return;`で関数を終えます。文が上から順に実行されるだけのシンプルな流れですが、「成功したら後片付けをしてから抜ける」という意図がコードの並び順そのものから読み取れます。

return route.submitMessage(data);

ここまでのどのifにも当てはまらなかった場合(通常時の送信)に実行される最後の行です。`route.submitMessage(data)`を呼び出し、その戻り値(`false | void`型)をそのまま`return`しています。呼び出した関数の戻り値をそのまま返すこのような書き方は、この関数自身の戻り値の型を素直に満たすための自然な終わり方です。

← 2026.09.16 の号を読む