モジュールの境界と関係

Collaboration Client、Transport、Endpoint、Service、Database Adapter の役割、データの流れ、組み立て方を理解します。

Univer の共同編集はブラウザー Client とサーバー側モジュールを組み合わせます。各モジュールは 1 種類の責任を持ちます。アプリケーションは OT1、プロトコル、永続化の内部実装に触れず、公開インターフェースで構成します。

全体のモジュール図

100%

Collaboration Client

ブラウザーの Collaboration Client は Web SDK と並行して動作し、次を担当します。

  • Unit のスナップショットと不足する確定変更セットを要求;
  • 使い捨て Session Ticket を取得して WebSocket を開く;
  • Unit Room に参加して Presence を交換;
  • ローカル mutation を変更セットとして送信;
  • ACK と配信を受信し、再接続後に不足分を補完。

Client はユーザーの信頼性を判断せず、データの正本でもありません。サーバーが適切な Middleware で本人確認と認可を実施します。

Node Transport

@univerjs-pro/collaboration-transport-node は Node.js の HTTP/WebSocket 入口です。ホスト HTTP サーバーからリクエストと WebSocket アップグレードを受け取り、入口の Middleware を実行し、Endpoint に通信を振り分けます。

Transport は OT、Unit データ、ACL モデルを理解しません。主な構成メソッドは 3 つです。

TypeScript
transport.use(httpMiddleware);transport.useUpgrade(upgradeMiddleware);transport.register(endpoint);
  • use() は通常 HTTP に参加し、userID、customData、ログ、追跡を設定できます。
  • useUpgrade() は WebSocket ハンドシェイクに参加し、接続前に拒否できます。
  • register() はプロトコル Endpoint を登録し、そのライフサイクルを Transport の管理下に置きます。

Collaboration Endpoint

@univerjs-pro/collaboration-endpoint は Univer Collaboration Client のプロトコルを実装し、次を担当します。

機能Endpoint の役割
内容の読み込みUnit のスナップショット、ブロック、不足する確定変更セットを Client に提供
変更送信変更セットを受け取り、ACK を返し、確定変更を Room に配信
Session 接続使い捨て Session Ticket を発行し WebSocket Session を確立・維持
Room の共同編集JOIN、LEAVE、メンバー状態、Presence を処理
Unit のライフサイクル削除・復元リクエストを Service 呼び出しに対応付け
複数ノード任意の pub/sub アダプターと共有チケットストアを通じて Room、Presence、配信を他ノードへ中継

Endpoint は OT やデータ保存を直接行いません。Service から正本の結果を得て、Client プロトコルに変換します。ドキュメント作成などの製品操作はアプリケーション API から Service を呼びます。

Collaboration Service

@univerjs-pro/collaboration-service はネットワークに依存しない Sheet、Doc、Slide、Board、Base の共同編集コアです。

  • Unit の復元データと確定変更セットを読む;
  • Unit Data やスナップショットから Unit を作成;
  • 変更セットを送信し OT を実行して連続するリビジョン2を進める;
  • (unitID, sid, reqId) で重複送信を識別;
  • Unit を論理削除、復元、完全削除;
  • Service Middleware を実行し確定後に Events を発行。

HTTP、WebSocket、ユーザー、ロール、ACL、階層、ファイル管理は提供しません。Endpoint はこれを使ってブラウザープロトコルを実装します。業務 API やバックグラウンドタスクから公開 API を直接呼ぶこともできます。

基本 API

機能Service API
Unit を読み込みgetUnitLoadData()、getUnitLoadDataWithBlocks()
増分データを読み取りgetChangesets()、getSheetBlock()
Unit を作成createUnitFromData()、createUnitFromSnapshot()
変更を送信submitChangeset()
削除と復元deleteUnits()、recoverUnits()

Database Adapter

Database Adapter は Service とストレージ間の契約で、次を保証します。

  • 初期スナップショットと依存データのアトミックな作成;
  • リビジョンの比較交換(CAS);
  • 変更セット送信の冪等性;
  • 全依存データの存在後にのみスナップショットを可視化;
  • Unit ライフサイクルの一括操作を全件成功か全件未実行にする。

SDK は Memory と SQLite 実装を提供します。他の DB は同じ契約を実装します。データベースアダプターを参照してください。

4 つの基本概念

概念意味
Unit一意の unitID と型で識別する、独立して読み込み・共同編集できる Univer 内容
Snapshotあるリビジョンの永続化された Unit 内容。変更可能なライブオブジェクトではない
Changeset基準リビジョンと冪等性識別子を持つ mutation の送信集合
Revision確定 Unit 状態の連続バージョン番号。初期値は 1

動作中の内容は Facade API または Command で変更します。スナップショットオブジェクトの書き換えは Client や Service のライブ Unit を更新しません。

Unit の読み込み

100%

Transport Middleware で設定した userID/customData は現在の HTTP リクエストと共に Service Middleware に入ります。読み取り可否はアプリケーションのポリシーが決め、直接の HTTP 読み取りに Room 参加は必須ではありません。

編集の送信

text
Facade / Command  → Mutation  → Client の変更セット  → Endpoint への送信  → Service Middleware  → OT + リビジョン CAS  → Adapter のアトミックなコミット  → 確定変更セット  → ACK / Room への配信

Database Adapter の確定リビジョンが正本です。ACK と配信はオンラインクライアントを低遅延で同期します。受信を逃したクライアントは既知のリビジョンから確定変更セットを取得できます。

最小のサーバー構成

TypeScript
import { createServer } from "node:http";import { MemoryDatabaseAdapter } from "@univerjs-pro/collaboration-database-memory";import { UniverCollabEndpoint } from "@univerjs-pro/collaboration-endpoint";import { UniverCollabService } from "@univerjs-pro/collaboration-service";import { createNodeTransport } from "@univerjs-pro/collaboration-transport-node";const database = new MemoryDatabaseAdapter();const service = new UniverCollabService({ dbAdapter: database });const endpoint = new UniverCollabEndpoint(service);const transport = createNodeTransport();transport.use(async (context, next) => {  context.userID = "demo-user";  await next();});transport.register(endpoint);const server = createServer();transport.attach(server);server.listen(3010);

固定ユーザーと Memory Adapter は構成の実演用です。アプリケーションは Middleware で実際の Context を設定し、データ要件に合う Adapter を選びます。

アプリケーション API で Unit を作成

独自の HTTP/RPC エンドポイントを定義し、認証、入力検証、業務検査後に上記 service を呼べます。

text
業務クライアント  → POST /api/units  → アプリケーションの認証と検証  → service.createUnitFromData()  → Database Adapter

次の Express 例は、認証 Middleware が response.locals.user を設定済みで、request.body.data が Workbook Data として検証済みである前提です。

TypeScript
import { randomUUID } from "node:crypto";import { json } from "express";import { UniverType } from "@univerjs/protocol";app.post("/api/units", json({ limit: "1mb" }), async (request, response, next) => {  try {    const user = response.locals.user as { readonly userID: string };    const unitID = randomUUID();    const result = await service.createUnitFromData(      {        type: UniverType.UNIVER_SHEET,        data: {          ...request.body.data,          id: unitID,          rev: 1,        },      },      {        userID: user.userID,        customData: { traceID: randomUUID() },      },    );    response.status(result.status === "created" ? 201 : 200).json(result);  } catch (error) {    next(error);  }});

POST /api/units はアプリケーションのルートであり Collaboration Client のプロトコルルートではありません。createUnitFromData() は Service の createUnit Middleware を実行します。製品レコード、所有者 ACL、エラーレスポンス変換、失敗時の補償はアプリケーションが担当します。

リソースの解放

transport.dispose() は register() した Endpoint を破棄します。Service と注入した Database Adapter はアプリケーションが別途破棄します。組み立ての逆順です。

TypeScript
await transport.dispose();await service.dispose();await database.dispose();

pub/sub アダプターを渡した場合は、Transport の後にアプリケーションがアダプターとその接続を破棄します。Endpoint は破棄しません。

Middleware と Eventsでこれらの境界での動作拡張に進みます。

Footnotes

  1. *操作変換(OT)*は、異なる文書リビジョンに対して同時に生成された操作を調整し、一貫して適用できるようにします。最後に届いたスナップショットで文書全体を置き換える方式ではありません。 ↩

  2. リビジョンは Unit の確定済み編集履歴における位置です。changeset は送信する操作の集合で、その確定により履歴が進みます。SDK パッケージのバージョンやタイムスタンプではありません。 ↩

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

© 2026 DreamNum Co., Ltd.