Middleware と Events

汎用 Middleware と Events で Transport、Endpoint、Service、共同編集の拡張モジュールを拡張します。

Middleware と Events は Collaboration SDK の汎用拡張ポイントです。本人確認と認可は Middleware で接続します。操作前の検証・制限には Middleware、状態確定後の変更記録や後続処理には Events を使います。どちらもログ、リクエスト追跡、メトリクス、派生状態の更新に使えます。

2 つの拡張タイミング

MiddlewareEvent
実行時点操作中状態変更の確定後
操作への参加・拒否可能不可
呼び出し Context の補完可能Event に含まれる Context と結果を読み取る
リスナーの失敗現在の操作が失敗する場合がある確定済みのドメイン結果から分離
主な用途本人確認、認可、検証、ログ、計時派生インデックス、キャッシュ、メトリクス、プロセス内通知

操作を続行できるか判断する場合は Middleware、状態変更が既に起きたことを観察する場合は Event を使います。

Middleware の実行モデル

同じ Action の Middleware は登録順の非同期チェーンになります。await next() が次の Middleware に入り、最内側で SDK の操作が実行されます。

TypeScript
service.use("submitChangeset", async (context, next) => {  const startedAt = performance.now();  context.customData.startedAt = startedAt;  try {    await next();  } finally {    metrics.observe("collaboration.submit.duration", performance.now() - startedAt);  }});

Middleware は次の処理を行えます。

  • Action 固有のリクエストを読む;
  • customData で呼び出し内データを共有する;
  • next() 前に入力を検査し、アプリケーションのシステムに問い合わせる;
  • next() が戻った後に時間や結果を記録する;
  • CollabError を投げ、想定された共同編集操作を拒否する。

Transport HTTP Middleware は next() を呼ばずに HTTP レスポンスを終了できます。Service と Endpoint の Middleware は、黙って next() を省略せず、明示的なエラーで拒否します。

Transport Middleware

transport.use() は通常の HTTP Middleware を登録し、登録順に実行します。await next() で後続の処理に進むか、レスポンスを直接終了できます。transport.useUpgrade() は WebSocket ハンドシェイクを処理します。

次の例では、HTTP の所要時間を記録し、認証して信頼できる userID を設定した後、Collaboration Endpoint を登録します。

TypeScript
transport.use(async (context, next) => {  const start = performance.now();  context.response.once("finish", () => {    recordHttpDuration({      method: context.incomingMessage.method,      route: context.route?.path ?? "unmatched",      status: context.response.statusCode,      durationMs: performance.now() - start,    });  });  await next();});transport.use(async (context, next) => {  const user = await authenticate(context.incomingMessage);  if (!user) {    context.response.statusCode = 401;    context.response.end("Authentication required");    return;  }  context.userID = user.userId;  await next();});transport.register(collaborationEndpoint);

recordHttpDuration と authenticate はアプリケーションが提供します。Endpoint は前段の Middleware が設定した userID を使用します。観測用 Middleware を認証より前に登録するため、認証による拒否も記録されます。

context.route?.path は一致した完全なルートテンプレートで、処理が Endpoint に到達した時点で利用できます。response の finish イベントでレスポンス完了までの時間を計測します。ルートの照合前に拒否されたリクエストや未知のパスは unmatched として記録されます。

Endpoint Middleware

Endpoint Middleware はリアルタイム Session と Presence の操作に参加します。

Action実行時点主な用途
connectチケット消費と Session 作成後、HELLO 前接続ポリシー、メンバーの表示名とアバター
joinUnitSession が Unit Room に参加する前Room への参加許可と Unit の可視性
receivePresence参加済み Session からの Presence 受信後、配信前検証と Presence フィルタリング
sendPresence各 Room メンバーへの Presence 送信前受信者ごとのフィルタリング
TypeScript
endpoint.use("receivePresence", async (context, next) => {  if (JSON.stringify(context.payload).length > 8_000) {    throw new CollabError("INVALID_REQUEST", "Presence payload is too large");  }  await next();});

Endpoint の session.userID と session.customData は Session Ticket を発行した HTTP リクエストに由来します。Endpoint が現在の WebSocket Session に作る memberID は再接続時に変わります。

Service Middleware

Service Middleware は正本の共同編集データのライフサイクルに参加します。

Action境界主な用途
readUnitDataスナップショット、ブロック、変更セットの読み取り前閲覧ポリシー、テナント検査、監査
createUnitUnit のアトミックな作成前型検査、作成ポリシー、業務ルール
deleteUnits一括削除の書き込み前削除ポリシーと Unit ごとの検査
recoverUnits一括復元の書き込み前復元ポリシーと監査
submitChangeset論理送信が冪等性処理と OT に入る前編集ポリシー、合計サイズ制限、追跡
applyChangeset変換済み変更セットを候補状態に適用する前mutation 単位の検証
commitChangesetAdapter のリビジョン CAS とコミットの前最終リビジョンのルールとメトリクス
TypeScript
service.use("createUnit", async (context, next) => {  await creationPolicy.assertAllowed({    userID: context.userID,    unitID: context.request.snapshot.unitID,    type: context.request.snapshot.type,  });  context.customData.creationPolicyChecked = true;  await next();});

submitChangeset は論理送信ごとに 1 回実行されます。リビジョン競合により applyChangeset と commitChangeset が新しい attempt で再実行される場合があります。この段階の Middleware は再試行に安全である必要があり、取り消せない外部メッセージの送信や一度限りの課金を直接行ってはいけません。

customData の範囲

customData はアプリケーションが書き込める呼び出し内オブジェクトで、次に適しています。

  • テナント ID、追跡 ID、リクエストロガー;
  • 現在の呼び出しで再利用する ACL 検索結果;
  • 計時開始点や監査ラベル;
  • 同じ Middleware チェーンで共有する業務オブジェクト。

通常の HTTP リクエスト、Service 呼び出し、WebSocket Session はライフサイクルが異なります。customData は自動保存、ブラウザー送信、ログ出力されません。長期データはアプリケーションのストレージに置きます。

Event の実行モデル

Service は on() でプロセス内リスナーを登録し、破棄可能な購読を受け取ります。

TypeScript
const subscription = service.on("changesetCommitted", async (event) => {  metrics.increment("collaboration.changeset.committed", {    unitType: String(event.changeset.type),  });  localCache.delete(event.changeset.unitID);});subscription.dispose();

同じ Event のリスナーは登録順に逐次実行され、完了が待機されます。エラーはログに記録され、確定済みのドメイン結果から分離されます。たとえば changesetCommitted リスナーが失敗しても Adapter が保存した変更セットはロールバックされません。

Events はプロセス内、復旧可能、または非クリティカルな後続処理に適しています。DB コミットと共通のトランザクション境界ではなく、外部システムへの配信を保証しません。

基本 Service の Events

Event確定した事実主な用途
unitCreatedUnit と初期データを作成済みローカル一覧キャッシュや派生インデックス
changesetCommitted変更セットを確定しリビジョンを更新済み配信、メトリクス、派生インデックス
unitsDeletedAdapter が一括削除を確定済みプロセス内の関連状態の消去
unitsRecoveredAdapter が一括復元を確定済みプロセス内派生状態の復元

Endpoint は同じ Service の changesetCommitted を監視し、現在のプロセスで Unit に参加したクライアントへ確定変更セットを配信します。pub/sub アダプターを設定している場合は他のノードにも届けます。また、明示的な退出、切断、Endpoint の破棄に対して memberLeftUnit を公開します。

拡張モジュールは独自の拡張ポイントを持つ

History、Thread Comment、Worktree は基本 Collaboration Service の Middleware や Events を継承しません。

  • History は getHistoryList、listHistoryCreators、getHistoryChangesets の読み取り Middleware を提供します。
  • Comment は追加、返信、編集、削除、解決、一覧の Middleware と commentCommitted を提供します。
  • Worktree はライフサイクルと下書き read/submit/commit の Middleware、および作成、状態、マージ、下書きコミットの Events を提供します。

有効にしたモジュールごとにルールを設定してください。基本 Service のログ、テナント、認可 Middleware は自動適用されません。

既存のユーザーや ACL をこれらの境界に対応付ける具体例は本人確認と認可に進んでください。

このガイドはいかがですか?

© 2026 DreamNum Co., Ltd.