# Etherpad コードリーディングガイドライン

## はじめに

このガイドラインは、Etherpadのコードベースを効率的に理解するための手引きです。
TypeScript/JavaScriptに精通していないエンジニアでも、段階的に学習できるよう構成されています。

**対象読者:**
- プロジェクトに新規参画するエンジニア
- 他言語からの経験者
- コードレビューを行う担当者

**Etherpadとは:**
Etherpadは、リアルタイム協調編集機能を提供するオープンソースのWebエディタです。複数のユーザーが同時に同じドキュメントを編集でき、変更がリアルタイムで同期されます。

---

## 1. 言語基礎

> このセクションでは、TypeScript/JavaScriptの基本構文と概念を解説します。

### 1.1 プログラム構造

Etherpadは**TypeScript**を主要言語として使用し、ESModule形式でモジュールを構成しています。

```typescript
// src/node/server.ts:1-8
#!/usr/bin/env node

/**
 * This module is started with src/bin/run.sh. It sets up a Express HTTP and a Socket.IO Server.
 * Static file Requests are answered directly from this module, Socket.IO messages are passed
 * to MessageHandler and minfied requests are passed to minified.
 */
```

**ポイント:**
- ファイル先頭に `'use strict';` を記述してstrict modeを有効化
- ESModule形式（`import`/`export`）を使用
- TypeScriptの型定義ファイルは `types/` ディレクトリに配置

### 1.2 データ型と変数

TypeScriptの型システムを活用し、型安全なコードを実現しています。

```typescript
// src/node/db/Pad.ts:42-50
class Pad {
  private db: Database;
  private atext: AText;
  private pool: AttributePool;
  private head: number;
  private chatHead: number;
  private publicStatus: boolean;
  private id: string;
  private savedRevisions: any[];
```

**主要な型定義:**
| 型 | 説明 | 例 |
|-----|------|-----|
| `AText` | 属性付きテキスト | `{text: string, attribs: string}` |
| `Changeset` | 変更セット | 差分情報を表現 |
| `AttributePool` | 属性プール | テキスト属性の管理 |
| `PadType` | Padオブジェクト型 | ドキュメントの状態 |

### 1.3 制御構造

非同期処理には`async/await`パターンを一貫して使用しています。

```typescript
// src/node/server.ts:108-127
exports.start = async () => {
  switch (state) {
    case State.INITIAL:
      break;
    case State.STARTING:
      await startDoneGate;
      return await exports.start();
    case State.RUNNING:
      return express.server;
    // ...
  }
  logger.info('Starting Etherpad...');
  // ...
};
```

### 1.4 関数/メソッド定義

関数は主に以下のパターンで定義されます:

```typescript
// src/node/handler/PadMessageHandler.ts:492-499
const handleChatMessage = async (socket: any, message: ChatMessageMessage) => {
  const chatMessage = ChatMessage.fromObject(message.data.message);
  const {padId, author: authorId} = sessioninfos[socket.id];
  // Don't trust the user-supplied values.
  chatMessage.time = Date.now();
  chatMessage.authorId = authorId;
  await exports.sendChatMessageToPadClients(chatMessage, padId);
};
```

**関数定義パターン:**
- アロー関数 (`const fn = async () => {}`)
- モジュールエクスポート (`exports.functionName = async () => {}`)
- クラスメソッド

### 1.5 モジュール/インポート

ESModule形式のimport/exportを使用しています:

```typescript
// src/node/db/Pad.ts:2-29
import {Database} from "ueberdb2";
import {AChangeSet, APool, AText} from "../types/PadType";
import {MapArrayType} from "../types/MapType";
import AttributeMap from '../../static/js/AttributeMap';
import ChatMessage from '../../static/js/ChatMessage';
import AttributePool from '../../static/js/AttributePool';
const Stream = require('../utils/Stream');  // CommonJSとの互換
```

**インポートパターン:**
- 名前付きインポート: `import { A, B } from 'module'`
- デフォルトインポート: `import A from 'module'`
- CommonJS互換: `const X = require('module')`

---

## 2. プロジェクト固有の概念

> このセクションでは、Etherpad特有の概念を解説します。

### 2.1 フレームワーク固有の概念

#### Express.js
HTTPサーバーフレームワークとしてExpressを使用:

```typescript
// src/node/hooks/express.ts:99-127
exports.restartServer = async () => {
  await closeServer();
  const app = express();

  if (settings.ssl) {
    const https = require('https');
    exports.server = https.createServer(options, app);
  } else {
    const http = require('http');
    exports.server = http.createServer(app);
  }
  // ...
};
```

#### Socket.IO
リアルタイム通信にSocket.IOを使用:

```typescript
// src/node/handler/PadMessageHandler.ts:163-176
exports.setSocketIO = (socket_io: any) => {
  socketio = socket_io;
};

exports.handleConnect = (socket: any) => {
  stats.meter('connects').mark();
  sessioninfos[socket.id] = {};
};
```

### 2.2 プロジェクト独自のパターン

#### Changesetシステム
差分同期の核となるChangeset（変更セット）システム:

```typescript
// src/static/js/Changeset.ts:87-93
/**
 * @typedef {object} Changeset
 * @property {number} oldLen - The length of the base document.
 * @property {number} newLen - The length of the document after applying the changeset.
 * @property {string} ops - Serialized sequence of operations.
 * @property {string} charBank - Characters inserted by insert operations.
 */
```

#### Hookシステム
プラグイン拡張のためのフックシステム:

```typescript
// src/static/js/pluginfw/hooks.ts:14
exports.deprecationNotices = {};

// フックの呼び出し例
await hooks.aCallAll('padCreate', {pad: this, authorId});
```

#### Attributeプール
テキスト属性（太字、色など）の効率的な管理:

```typescript
// src/node/db/Pad.ts:69-71
apool() {
  return this.pool;
}
```

---

## 3. 命名規則

> このセクションでは、プロジェクト全体で使用される命名規則を解説します。

### 3.1 ファイル・ディレクトリ命名

| パターン | 意味 | 例 |
|---------|------|-----|
| `PascalCase.ts` | クラス定義ファイル | `Pad.ts`, `ChatMessage.ts`, `AttributePool.ts` |
| `camelCase.ts` | ユーティリティ/関数ファイル | `padaccess.ts`, `randomstring.ts` |
| `PascalCaseHandler.ts` | ハンドラファイル | `PadMessageHandler.ts`, `ImportHandler.ts` |
| `PascalCaseManager.ts` | マネージャファイル | `PadManager.ts`, `AuthorManager.ts` |
| `lowercase/` | ディレクトリ名 | `node/`, `db/`, `hooks/`, `utils/` |

### 3.2 クラス・関数・変数命名

| プレフィックス/サフィックス | 意味 | 例 |
|---------------------------|------|-----|
| `get*` | 取得メソッド | `getHeadRevisionNumber()`, `getRevisionChangeset()` |
| `set*` | 設定メソッド | `setPublicStatus()`, `setText()` |
| `handle*` | イベントハンドラ | `handleMessage()`, `handleConnect()` |
| `*Manager` | 管理クラス | `PadManager`, `AuthorManager` |
| `*Handler` | 処理ハンドラ | `PadMessageHandler`, `ImportHandler` |
| `*Type` | 型定義 | `PadType`, `MapType` |
| `is*`, `has*`, `can*` | 真偽値 | `isReadOnly`, `hasMarker` |
| `_*` | プライベート/内部使用 | `_getRoomSockets()`, `_correctMarkersInPad()` |

### 3.3 プログラム分類一覧

| カテゴリ | ディレクトリ | 説明 |
|---------|-------------|------|
| サーバーコア | `src/node/` | バックエンド処理 |
| DBアクセス | `src/node/db/` | データベース操作 |
| ハンドラ | `src/node/handler/` | リクエスト/メッセージ処理 |
| ユーティリティ | `src/node/utils/` | 共通ユーティリティ |
| フック | `src/node/hooks/` | Express/プラグインフック |
| クライアント | `src/static/js/` | フロントエンド処理 |
| プラグインFW | `src/static/js/pluginfw/` | プラグインフレームワーク |
| 型定義 | `src/node/types/` | TypeScript型定義 |

---

## 4. ディレクトリ構造

> このセクションでは、プロジェクトのディレクトリ構造を解説します。

```
etherpad-lite-develop/
├── src/                      # メインソースコード
│   ├── node/                 # サーバーサイド（Node.js）
│   │   ├── db/              # データベースアクセス層
│   │   ├── handler/         # リクエスト/メッセージハンドラ
│   │   ├── hooks/           # Expressフック定義
│   │   │   └── express/     # Express関連フック
│   │   ├── security/        # セキュリティ関連
│   │   ├── types/           # TypeScript型定義
│   │   ├── utils/           # ユーティリティ関数
│   │   └── server.ts        # エントリーポイント
│   ├── static/              # 静的ファイル（フロントエンド）
│   │   └── js/              # JavaScriptモジュール
│   │       ├── pluginfw/    # プラグインフレームワーク
│   │       ├── types/       # クライアント側型定義
│   │       └── vendors/     # サードパーティライブラリ
│   └── tests/               # テストコード
│       ├── backend/         # バックエンドテスト
│       ├── backend-new/     # 新バックエンドテスト(vitest)
│       ├── frontend/        # フロントエンドテスト
│       └── frontend-new/    # Playwrightテスト
├── admin/                    # 管理画面（React）
│   └── src/                 # 管理画面ソース
├── ui/                       # UIコンポーネント
├── bin/                      # スクリプト/ツール
├── doc/                      # ドキュメント
└── local_plugins/            # ローカルプラグイン
```

### 各ディレクトリの役割

| ディレクトリ | 役割 | 主要ファイル |
|-------------|------|-------------|
| `src/node/` | サーバーサイドコード | `server.ts` |
| `src/node/db/` | データベース操作 | `Pad.ts`, `DB.ts`, `PadManager.ts` |
| `src/node/handler/` | Socket.IO/APIハンドラ | `PadMessageHandler.ts`, `APIHandler.ts` |
| `src/node/hooks/express/` | Expressミドルウェア | `webaccess.ts`, `socketio.ts` |
| `src/node/utils/` | ユーティリティ | `Settings.ts`, `Minify.ts` |
| `src/static/js/` | クライアントサイドJS | `pad.ts`, `Changeset.ts`, `collab_client.ts` |
| `admin/` | 管理画面React SPA | `App.tsx`, `pages/` |

---

## 5. アーキテクチャ

> このセクションでは、プロジェクトのアーキテクチャパターンを解説します。

### 5.1 全体アーキテクチャ

Etherpadは**リアルタイム協調編集**を実現するための3層アーキテクチャを採用しています。

```mermaid
graph TB
    subgraph "クライアント層"
        B[ブラウザ]
        A[管理画面]
    end

    subgraph "サーバー層"
        E[Express HTTP]
        S[Socket.IO]
        H[Hooks System]
    end

    subgraph "データ層"
        DB[(UeberDB)]
        P[プラグイン]
    end

    B <-->|WebSocket| S
    B <-->|HTTP| E
    A <-->|HTTP| E
    E --> H
    S --> H
    H --> DB
    H --> P
```

### 5.2 レイヤー構成

| レイヤー | 責務 | 代表的なファイル |
|---------|------|-----------------|
| プレゼンテーション | UI表示、ユーザー入力 | `src/static/js/pad.ts`, `admin/src/` |
| 通信 | HTTP/WebSocket通信 | `src/node/hooks/express.ts`, `PadMessageHandler.ts` |
| ビジネスロジック | Changeset処理、同期 | `Changeset.ts`, `collab_client.ts` |
| データアクセス | DB操作、永続化 | `src/node/db/Pad.ts`, `DB.ts` |

### 5.3 データフロー

#### リアルタイム編集のデータフロー

```
1. ユーザー入力
   ↓
2. クライアント: Changeset生成 (collab_client.ts)
   ↓
3. WebSocket送信 (USER_CHANGES)
   ↓
4. サーバー: 受信・検証 (PadMessageHandler.ts:627-736)
   ↓
5. Changeset変換・適用 (Pad.ts:97-144)
   ↓
6. DB永続化 (DB.ts)
   ↓
7. 他クライアントへブロードキャスト (NEW_CHANGES)
```

---

## 6. 主要コンポーネント

> このセクションでは、主要なコンポーネントとその連携を解説します。

### 6.1 エントリーポイント

サーバー起動は `src/node/server.ts` から開始:

```typescript
// src/node/server.ts:130-184
try {
  check();  // バージョンチェック

  stats.gauge('memoryUsage', () => process.memoryUsage().rss);

  await db.init();                    // DB初期化
  await checkForMigration();          // マイグレーション
  await plugins.update();             // プラグイン読み込み
  await hooks.aCallAll('loadSettings', {settings});
  await hooks.aCallAll('createServer');  // サーバー作成
} catch (err) {
  // エラー処理
}
```

### 6.2 ビジネスロジック

#### Padクラス（ドキュメントモデル）

```typescript
// src/node/db/Pad.ts:41-67
class Pad {
  constructor(id: string, database = db) {
    this.db = database;
    this.atext = makeAText('\n');     // 属性付きテキスト
    this.pool = new AttributePool();  // 属性プール
    this.head = -1;                   // 最新リビジョン
    this.chatHead = -1;               // 最新チャット
    this.publicStatus = false;
    this.id = id;
    this.savedRevisions = [];
  }
}
```

#### Changeset処理

```typescript
// src/node/db/Pad.ts:97-144
async appendRevision(aChangeset: string, authorId = '') {
  const newAText = applyToAText(aChangeset, this.atext, this.pool);
  // 変更なしの場合はスキップ
  if (newAText.text === this.atext.text &&
      newAText.attribs === this.atext.attribs &&
      this.head !== -1) {
    return this.head;
  }
  copyAText(newAText, this.atext);
  const newRev = ++this.head;
  // DBに保存 & フック呼び出し
  await Promise.all([
    this.db.set(`pad:${this.id}:revs:${newRev}`, {...}),
    this.saveToDatabase(),
    hooks.aCallAll(hook, {...}),
  ]);
  return newRev;
}
```

### 6.3 データアクセス

UeberDB2を使用した抽象化されたDB操作:

```typescript
// src/node/db/DB.ts:39-54
exports.init = async () => {
  exports.db = new Database(settings.dbType, settings.dbSettings, null, logger);
  await exports.db.init();
  // メトリクス設定
  for (const fn of ['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove']) {
    const f = exports.db[fn];
    exports[fn] = async (...args) => await f.call(exports.db, ...args);
  }
};
```

### 6.4 ユーティリティ/共通機能

#### Settings（設定管理）

```typescript
// src/node/utils/Settings.ts:92-131
const parseSettings = (settingsFilename: string, isSettings: boolean) => {
  let settingsStr = '';
  try {
    settingsStr = fs.readFileSync(settingsFilename).toString();
  } catch (e) {
    // ファイルがない場合の処理
  }
  settingsStr = jsonminify(settingsStr);
  const settings = JSON.parse(settingsStr);
  return lookupEnvironmentVariables(settings);
};
```

---

## 7. よく使われるパターン

> このセクションでは、コード内で頻出するパターンを解説します。

### パターン一覧

| パターン | 説明 | 出現頻度 | 代表的なファイル |
|---------|------|---------|-----------------|
| async/await | 非同期処理 | 高 | 全ファイル |
| Hook呼び出し | プラグイン拡張ポイント | 高 | `server.ts`, `Pad.ts` |
| エクスポートパターン | モジュール公開 | 高 | 全ファイル |
| ステートマシン | 状態遷移管理 | 中 | `server.ts` |
| チャネル/キュー | 順序保証処理 | 中 | `PadMessageHandler.ts` |

### 各パターンの詳細

#### パターン1: async/awaitによる非同期処理

**目的:** コールバック地獄を避け、読みやすい非同期コードを実現

**実装例:**
```typescript
// src/node/db/Pad.ts:382-400
async init(text: string, authorId = '') {
  const value = await this.db.get(`pad:${this.id}`);

  if (value != null) {
    Object.assign(this, value);
    if ('pool' in value) this.pool = new AttributePool().fromJsonable(value.pool);
  } else {
    if (text == null) {
      const context = {pad: this, authorId, type: 'text', content: settings.defaultPadText};
      await hooks.aCallAll('padDefaultContent', context);
      text = exports.cleanText(context.content);
    }
    const firstChangeset = makeSplice('\n', 0, 0, text);
    await this.appendRevision(firstChangeset, authorId);
  }
  await hooks.aCallAll('padLoad', {pad: this});
}
```

**解説:** Promiseを返す関数を`await`で待機し、同期的なコードのように記述

#### パターン2: Hookシステム

**目的:** プラグインによる機能拡張を可能にする

**実装例:**
```typescript
// src/node/server.ts:183-184
await hooks.aCallAll('loadSettings', {settings});
await hooks.aCallAll('createServer');

// src/node/db/Pad.ts:110-141
const hook = this.head === 0 ? 'padCreate' : 'padUpdate';
await hooks.aCallAll(hook, {
  pad: this,
  authorId,
  // ...
});
```

**解説:** イベント名とコンテキストオブジェクトを渡してプラグインを呼び出し

#### パターン3: ステートマシン

**目的:** サーバーの状態遷移を明確に管理

**実装例:**
```typescript
// src/node/server.ts:86-95
const State = {
  INITIAL: 1,
  STARTING: 2,
  RUNNING: 3,
  STOPPING: 4,
  STOPPED: 5,
  EXITING: 6,
  WAITING_FOR_EXIT: 7,
  STATE_TRANSITION_FAILED: 8,
};
```

**解説:** 列挙型で状態を定義し、switch文で遷移を制御

#### パターン4: チャネル/キューパターン

**目的:** 同一Padへの変更を順序保証して処理

**実装例:**
```typescript
// src/node/handler/PadMessageHandler.ts:121-152
class Channels {
  private readonly _exec: (ch: any, task: any) => any;
  private _promiseChains: Map<any, Promise<any>>;

  async enqueue(ch: any, task: any): Promise<any> {
    const p = (this._promiseChains.get(ch) || Promise.resolve())
      .then(() => this._exec(ch, task));
    // ...
  }
}

const padChannels = new Channels((ch, {socket, message}) =>
  handleUserChanges(socket, message));
```

**解説:** Pad IDごとにPromiseチェーンを管理し、順序を保証

---

## 8. 業務フロー追跡の実践例

> このセクションでは、実際の業務フローをコードで追跡する方法を解説します。

### 8.1 フロー追跡の基本手順

1. エントリーポイントを特定
2. 処理の流れを追跡（呼び出し関係を追う）
3. データの変換を確認
4. 最終的な出力を確認

### 8.2 フロー追跡の実例

#### 例1: テキスト編集の同期フロー

**概要:** ユーザーがパッドでテキストを編集し、変更が他のユーザーに同期されるまでの流れ

**処理フロー:**
```
ブラウザ入力 → collab_client → Socket.IO → PadMessageHandler → Pad → DB → 他クライアント
```

**詳細な追跡:**

1. **クライアント側: 変更検出** (`src/static/js/collab_client.ts`)
   - ACE エディタが変更を検出
   - Changeset を生成

2. **クライアント側: 送信** (`src/static/js/collab_client.ts`)
   - USER_CHANGES メッセージをSocket.IO経由で送信

3. **サーバー側: 受信** (`src/node/handler/PadMessageHandler.ts:273-436`)
   ```typescript
   exports.handleMessage = async (socket: any, message: ClientVarMessage) => {
     // レート制限チェック
     await rateLimiter.consume(socket.request.ip);
     // セッション確認
     const thisSession = sessioninfos[socket.id];
     // メッセージタイプに応じた処理
     switch (type) {
       case 'COLLABROOM':
         switch (message.data.type) {
           case 'USER_CHANGES':
             await padChannels.enqueue(thisSession.padId, {socket, message});
             break;
         }
     }
   };
   ```

4. **サーバー側: Changeset処理** (`src/node/handler/PadMessageHandler.ts:627-736`)
   ```typescript
   const handleUserChanges = async (socket: any, message) => {
     const {data: {baseRev, apool, changeset}} = message;
     const pad = await padManager.getPad(thisSession.padId);

     // Changesetの検証
     checkRep(changeset);

     // リベース処理（他の変更との統合）
     while (r < pad.getHeadRevisionNumber()) {
       r++;
       const {changeset: c} = await pad.getRevision(r);
       rebasedChangeset = follow(c, rebasedChangeset, false, pad.pool);
     }

     // Padに適用
     const newRev = await pad.appendRevision(rebasedChangeset, thisSession.author);

     // 確認応答
     socket.emit('message', {type: 'COLLABROOM', data: {type: 'ACCEPT_COMMIT', newRev}});

     // 他クライアントに配信
     await exports.updatePadClients(pad);
   };
   ```

5. **サーバー側: DB保存** (`src/node/db/Pad.ts:97-144`)
   ```typescript
   async appendRevision(aChangeset: string, authorId = '') {
     const newAText = applyToAText(aChangeset, this.atext, this.pool);
     copyAText(newAText, this.atext);
     const newRev = ++this.head;
     await Promise.all([
       this.db.set(`pad:${this.id}:revs:${newRev}`, {...}),
       this.saveToDatabase(),
     ]);
     return newRev;
   }
   ```

6. **サーバー側: ブロードキャスト** (`src/node/handler/PadMessageHandler.ts:738-795`)
   ```typescript
   exports.updatePadClients = async (pad: PadType) => {
     const roomSockets = _getRoomSockets(pad.id);
     await Promise.all(roomSockets.map(async (socket) => {
       while (sessioninfo.rev < pad.getHeadRevisionNumber()) {
         const revision = await pad.getRevision(r);
         socket.emit('message', {
           type: 'COLLABROOM',
           data: {type: 'NEW_CHANGES', newRev: r, changeset, ...}
         });
       }
     }));
   };
   ```

#### 例2: パッド作成フロー

**概要:** 新しいパッドが作成される流れ

**処理フロー:**
```
URLアクセス → Express → PadManager → Pad.init() → DB保存 → クライアント応答
```

**詳細な追跡:**

1. **URLアクセス** (`src/node/hooks/express/specialpages.ts`)
   - `/p/:padId` へのリクエストを処理

2. **パッド取得/作成** (`src/node/db/PadManager.ts`)
   ```typescript
   exports.getPad = async (padId, text, authorId) => {
     if (!await exports.doesPadExist(padId)) {
       // 新規作成
       const pad = new Pad(padId);
       await pad.init(text, authorId);
       return pad;
     }
   };
   ```

3. **初期化** (`src/node/db/Pad.ts:382-401`)
   ```typescript
   async init(text: string, authorId = '') {
     const value = await this.db.get(`pad:${this.id}`);
     if (value == null) {
       const context = {pad: this, type: 'text', content: settings.defaultPadText};
       await hooks.aCallAll('padDefaultContent', context);
       text = exports.cleanText(context.content);
       const firstChangeset = makeSplice('\n', 0, 0, text);
       await this.appendRevision(firstChangeset, authorId);
     }
     await hooks.aCallAll('padLoad', {pad: this});
   }
   ```

### 8.3 フロー追跡チェックリスト

- [ ] エントリーポイントを特定したか
- [ ] 呼び出し関係を把握したか
- [ ] データの変換ポイントを確認したか
- [ ] エラーハンドリングを確認したか
- [ ] 最終的な出力を確認したか

---

## 9. 設計書の参照順序

> このセクションでは、プロジェクト理解のための設計書参照順序を案内します。

### 9.1 目的別ロードマップ

#### 全体像を把握したい場合
1. `README.md` - プロジェクト概要
2. `docs/code-to-docs/アーキテクチャ設計書.md` - システム構成
3. `docs/code-to-docs/機能一覧.csv` - 機能リスト

#### 特定機能を理解したい場合
1. `docs/code-to-docs/機能一覧.csv` - 機能を特定
2. `docs/code-to-docs/画面設計書/` - UI仕様
3. 関連ソースコード

#### 改修作業を行う場合
1. 対象機能の設計書
2. 影響範囲確認（呼び出し関係）
3. テストケース（`src/tests/`）

### 9.2 ドキュメント一覧

| ドキュメント | 概要 | 参照タイミング |
|-------------|------|---------------|
| README.md | プロジェクト概要・セットアップ | 初回参照 |
| CHANGELOG.md | バージョン履歴 | リリース確認時 |
| settings.json.template | 設定サンプル | 環境構築時 |
| doc/api/ | HTTP API仕様 | API開発時 |
| doc/plugins.md | プラグイン開発ガイド | プラグイン作成時 |

---

## 10. トラブルシューティング

> このセクションでは、コードリーディング時によくある問題と解決法を解説します。

### よくある疑問と回答

#### Q: Changesetの形式がわからない
A: `src/static/js/Changeset.ts` のドキュメントコメントを参照してください。形式は `Z:oldLen>newLen|lines=chars|...` です。

#### Q: プラグインフックの一覧はどこにあるか
A: `doc/api/hooks_overview.md` を参照するか、ソースコード内で `hooks.aCallAll` を検索してください。

#### Q: Socket.IOメッセージの種類は
A: `src/node/handler/PadMessageHandler.ts:394-436` の switch 文を参照してください。主要なものは:
- `CLIENT_READY`: 接続確立
- `USER_CHANGES`: 編集操作
- `CHAT_MESSAGE`: チャットメッセージ

#### Q: データベースのスキーマは
A: 明示的なスキーマ定義はありません。キー/値形式で以下のパターンを使用:
- `pad:{padId}` - パッドメタデータ
- `pad:{padId}:revs:{revNum}` - リビジョンデータ
- `pad:{padId}:chat:{chatNum}` - チャットメッセージ
- `author:{authorId}` - 作者情報

### デバッグのヒント

1. **ログ確認**: `log4js` ロガーを使用。ログレベルは `settings.json` で設定
2. **Socket.IO通信**: `DEBUG=socket.io* npm run dev` で詳細ログ出力
3. **DBデータ確認**: `bin/checkPad.ts` スクリプトでパッドデータを検証
4. **テスト実行**: `npm test` でバックエンドテスト実行

---

## 付録

### A. 用語集

| 用語 | 説明 |
|-----|------|
| Pad | Etherpadのドキュメント単位 |
| Changeset | テキスト変更を表現する差分形式 |
| AText | 属性付きテキスト（Attributed Text） |
| AttributePool | テキスト属性（太字等）の番号管理プール |
| Revision | パッドの特定時点のスナップショット |
| Author | 編集者（固有ID割り当て） |
| Hook | プラグイン拡張ポイント |
| Collabroom | リアルタイム協調編集用Socket.IOルーム |

### B. ファイル一覧

| ファイル/ディレクトリ | 説明 | 主な内容 |
|---------------------|------|---------|
| `src/node/server.ts` | サーバーエントリーポイント | 起動・終了処理 |
| `src/node/db/Pad.ts` | Padクラス定義 | ドキュメントモデル |
| `src/node/db/DB.ts` | DB抽象化 | UeberDB2ラッパー |
| `src/node/handler/PadMessageHandler.ts` | Socket.IOハンドラ | リアルタイム同期 |
| `src/node/utils/Settings.ts` | 設定管理 | settings.json読み込み |
| `src/static/js/Changeset.ts` | Changeset処理 | 差分計算・適用 |
| `src/static/js/collab_client.ts` | 協調編集クライアント | WebSocket通信 |
| `src/static/js/pad.ts` | パッドUI | フロントエンド初期化 |

### C. 参考資料

- [Etherpad公式サイト](https://etherpad.org/)
- [GitHub リポジトリ](https://github.com/ether/etherpad-lite)
- [TypeScript ドキュメント](https://www.typescriptlang.org/docs/)
- [Express.js ガイド](https://expressjs.com/)
- [Socket.IO ドキュメント](https://socket.io/docs/)
- [EasySync アルゴリズム解説](https://github.com/ether/etherpad-lite/blob/develop/doc/easysync/)
