Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Understanding Cordis: The TypeScript Framework Built for Hot-Swapping Everything

2026年09月12日に「Dev.to」が公開したITニュース「Understanding Cordis: The TypeScript Framework Built for Hot-Swapping Everything」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

CordisはTypeScriptのフレームワークで、アプリを止めずに機能(プラグイン)を動的に追加・更新・削除でき、メモリリークを自動で防ぐ。これにより安定稼働するシステムを構築可能。AIエージェントのDeepSeek Harnessの基盤にも使われている。

ITニュース解説

Cordisは、TypeScriptで開発されたオープンソースの「メタフレームワーク」だ。メタフレームワークとは、単一のアプリケーションを作るだけでなく、他のモジュール化されたフレームワークを作るための土台となるものを指す。Cordisの最大の特徴は、アプリケーションが稼働中に、その構成要素であるプラグインを動的に「ホットスワップ」できる点にある。つまり、システムを停止させることなく、新しい機能を追加したり、既存の機能を更新したり、不要な機能を削除したりできる。しかも、この動的な変更はメモリリークを引き起こさず、プロセスを再起動する必要もない。

なぜこのような機能が必要なのだろうか。ほとんどのバックエンドフレームワークは、アプリケーションが一度起動したら、その設定は固定され、プロセスが終了するまで変わらないという前提で設計されている。もしデータベース設定を変更したり、新しいAPIルートを追加したり、既存のモジュールを更新したりする場合、通常はNode.jsプロセス全体を再起動するしかない。しかし、常に稼働し続ける必要のあるシステム、例えばDiscordやTelegramなどのプラットフォームと常時接続を維持するチャットボットアプリケーションでは、プラグインの更新のために接続を切断するのはユーザー体験を著しく損なう。Cordisは、このようなニーズから生まれた。Koishiというチャットボットフレームワークの根幹技術として5年以上実戦で使われ、その信頼性が証明されてきた。

近年、この動的なプラグイン管理の考え方は、自律型AIエージェントの分野でも重要視されている。DeepSeek Harness(DSH)のような自律型AIエージェントは、長時間にわたって無人でコードを生成したり、サンドボックス環境を構築したり、動的なツールを登録したり、ファイル操作を行ったりする。従来のモノリシックな(単一巨大な)アーキテクチャでは、これらの操作が繰り返されるうちに、不要なイベントリスナーがメモリに残り続けたり、子プロセスがゾンビ化したり、メモリが肥大化したりといった「ランタイムのライフサイクル問題」が発生しやすかった。Cordisは、AIエージェントの実行環境を安定して管理するための「マイクロカーネル」(OSの機能を最小限の核で提供する考え方)として採用されている。これにより、AIエージェントが長時間稼働しても、リソースのリークを防ぎ、クリーンな状態を保つことが可能になる。

動的なプラグインがメモリリークを引き起こしやすい原因は、Node.jsのイベントシステムにある。例えば、プラグインがイベントリスナーを登録すると、そのリスナーはイベントエミッター(イベントを発生させる側)にメモリ参照として保持される。プラグインを「削除」しても、この参照が残っていれば、リスナーのコールバック関数もメモリに残り続ける。さらに、setInterval()のようなタイマーを設定した場合、タイマーハンドルがNode.jsのイベントループ全体を稼働させ続けるため、プラグインがアンロードされてもそのタイマーが止まらず、やはりメモリリークにつながる。このように、標準的なNode.jsアプリケーションでプラグインを繰り返しロード・アンロードすると、やがて「ディスポーザルアビス」(破棄の深淵)と呼ばれる問題、つまりゾンビ化したイベントリスナー、重複するハンドラ実行、宙ぶらりんのソケット、そして最終的にはメモリ不足によるクラッシュが発生してしまうのだ。

Cordisがこの問題を解決する核となるアイデアは、「すべての副作用はデフォルトで可逆であるべきだ」という哲学に基づいている。これは「すべての行動には取り消し(Undo)がある」と例えられる。プラグイン開発者が複雑なクリーンアップ関数を手動で記述する代わりに、Cordisは「制御の反転」によって副作用を管理する。プラグインが実行される際、CordisはContext(ctx)という特別な、隔離された環境を提供する。プラグインが外部に影響を与える操作(イベントの購読、タイマーの設定、サービスの登録など)を行うときは常に、このctxを通じて行う。Cordisは、プラグインが行ったそれぞれの操作に対して、自動的にその「undo」操作(クリーンアップ関数)を内部のクリーンアップスタックに記録する。そして、プラグインがアンロードされると、Cordisはこのスタックを逆順にたどり、すべての副作用を自動的に元に戻す。これにより、不要なリソースが確実に解放され、メモリリークが防止される。

この仕組みを、Cordisは「時空間的構成可能性」と呼ぶ。 「時間的」には、状態を残すことなく、アプリケーションの時間を前進(ロード)させたり後退(アンロード)させたりできることを意味する。 「空間的」には、モジュール同士が互いに安全に共存し、互いの機能を発見できることを意味する。 これは単なるソフトウェアの工夫ではなく、数学的に動的なシステムが安全にモジュールをロード・リロード・アンロードできることを証明した研究論文に裏付けられている。

Cordisの内部では、主に四つのコアな概念が使われている。 一つ目は「Context(コンテキスト)」だ。これはCordisにおける中心的なオブジェクトで、コンポーネントが動作するスコープを表す。アプリケーションの起動時に「ルートコンテキスト」が作られ、プラグインをロードする際には、そのプラグイン専用の「子コンテキスト」が作成される。CordisはJavaScriptのProxy機能を使ってこれらのコンテキストをラップし、プラグインがctx.databaseのようなプロパティにアクセスする際に、そのサービスが利用可能か、隔離されているかなどを動的にチェックし、プラグインが何を使っているかを追跡する。

二つ目は「Reversible Effects(可逆的な副作用)」だ。ctx.effect()という関数を使って、後でクリーンアップが必要な副作用を登録する。これはReactのuseEffect()に似ているが、バックエンドサーバーや長時間稼働するサービスのために設計されている。例えば、タイマーを設定する際に、ctx.effect()の引数として、タイマーを開始する関数を渡し、その戻り値としてタイマーを停止するクリーンアップ関数を返すと、プラグインがアンロードされたときにCordisが自動的にそのクリーンアップ関数を呼び出し、タイマーを停止する。

三つ目は「Services(サービス)」だ。サービスは、データベースクライアント、HTTPサーバー、ロガーなど、他のプラグインに提供される再利用可能な単一の機能オブジェクトを指す。Serviceクラスを継承して作成し、TypeScriptの「宣言マージ」という機能を利用して、declare module 'cordis'ブロックでコンテキストにサービスが存在することを宣言する。これにより、デコレータや複雑な依存性注入(DI)コンテナを使うことなく、ctx.databaseのように型安全でオートコンプリートが効く形でサービスを利用できるようになる。

四つ目は「Fiber(ファイバー)」だ。プラグインをロードするたびに、Cordisは舞台裏で軽量なランタイムマネージャーであるFiberを作成する。Fiberはプラグインの状態(PENDING, ACTIVE, DISPOSED)を追跡する役割を持つ。例えば、あるプラグインがデータベースサービスを必要と宣言している場合、データベースサービスがまだ存在しなければ、そのプラグインはPENDING状態(保留中)で待機する。データベースサービスがロードされた瞬間、Cordisはそれを検知し、即座にプラグインをACTIVE状態(活動中)に起動する。逆に、データベースサービスがアンロードされると、Cordisは自動的にそのプラグインを一時停止させ、クリーンアップ関数を実行する。これにより、存在しないデータベースを使おうとしてプラグインがクラッシュするのを防ぐ「リアクティブな依存解決」が可能になる。

Cordisのイベントシステムも特徴的だ。標準のNode.jsイベントエミッターは、すべてのリスナーを同期的に呼び出すだけで、その戻り値は気にしない。しかしCordisは、より実用的なアプリケーションのワークフローに対応するため、複数のディスパッチモードを導入している。例えば、「Bailパターン」では、複数のリスナーのうち、最初に何らかの有効な値を返した時点で処理を中断し、その値を返す。これは認証処理など、いずれかのモジュールが決定的な判断を下せば十分なケースで役立つ。「Waterfallパターン」では、各リスナーが前のリスナーの出力を受け取り、それを加工して次のリスナーに渡していく。これはテキスト処理パイプラインのように、段階的にデータを変換するようなケースで有効だ。

他のエコシステムと比較すると、Cordisの独自性がより明確になる。 JavaのOSGiは動的なモジュール性という点でCordisに最も近いが、各プラグインを独自のClassLoaderで隔離するため、依存ライブラリのバージョン違いによる「ClassLoader Hell」問題や、手動でのクリーンアップ忘れによるメモリリークのリスクが高かった。Cordisは単一のJavaScriptランタイム内でProxyと自動クリーンアップスタックを利用することで、これらの問題を回避する。 JavaのSpring Bootは静的な依存性注入が主流で、アプリケーション起動時に依存関係が固定されるため、ランタイムでの動的な変更には対応しない。Cordisは、サービスが動的に現れたり消えたりしても、依存するプラグインが自動的に一時停止したり再開したりする「リビングでリアクティブな」モデルを採用している。 TypeScriptのNestJSはSpringのような依存性注入を利用するが、デコレータやリフレクションに大きく依存し、モジュールの動的なアンロードは事実上不可能だ。Cordisはデコレータを使わずに、TypeScriptの宣言マージとランタイムProxyだけで型安全な依存性注入と動的なアンロードを実現する。

まとめると、CordisはJava OSGiの動的なサービスライフサイクル、Springの依存性注入、そしてReactのuseEffectのようなクリーンアップのしやすさを、すべて軽量なTypeScriptパッケージに凝縮したものと言える。長年の実戦経験に裏打ちされた信頼性、ホットリロード、ゼロメモリリーク、純粋TypeScriptの快適な開発体験、そしてリアクティブな依存性注入といった特長を持つ。 このCordisの核となる仕組みが、DeepSeek Harnessのような自律型AIエージェントにおいて、どのように安全で動的なツールサンドボックスを提供し、マルチステップの推論プロセスにおけるクリーンなリソース解放を実現するのかは、今後の解説で詳しく述べられる予定だ。

関連コンテンツ

関連IT用語

関連ITニュース