-
最新のテクノロジースタック
- Rust/Wasmコアで高性能
- TypeScriptバインディング
- メモリ効率の良い実装
-
充実した機能セット
- JSON構造のCRDT
- テキストCRDT
- バイナリデータサポート
- カスタムCRDTの定義
-
優れた開発者エクスペリエンス
- 型安全なAPI
- 豊富なドキュメント
- アクティブなコミュニティ
import { Automerge } from '@automerge/automerge'
import { AutomergeUrl } from '@automerge/automerge-repo'
import { BroadcastChannelNetworkAdapter } from '@automerge/automerge-repo-network-broadcastchannel'
import { IndexedDBStorageAdapter } from '@automerge/automerge-repo-storage-indexeddb'
// ドキュメントの型定義
interface CollaborativeDoc {
content: string
cursor: {
anchor: number
head: number
}
metadata: {
title: string
lastModified: number
}
}
// Automergeレポジトリの設定
const repo = new Automerge.Repo({
network: [new BroadcastChannelNetworkAdapter()],
storage: new IndexedDBStorageAdapter(),
})
// Reactコンポーネント
function CollaborativeEditor() {
const [doc, changeDoc] = useAutomerge<CollaborativeDoc>(docUrl)
const handleChange = (newContent: string) => {
changeDoc(doc => {
doc.content = newContent
doc.metadata.lastModified = Date.now()
})
}
const handleCursorMove = (anchor: number, head: number) => {
changeDoc(doc => {
doc.cursor = { anchor, head }
})
}
return (
<Editor
value={doc.content}
cursor={doc.cursor}
onChange={handleChange}
onCursorMove={handleCursorMove}
/>
)
}
import gleam/automerge.{type Doc}
import gleam/json
import gleam/websocket
pub type ServerDoc {
ServerDoc(
id: String,
doc: Doc,
connections: List(Connection),
)
}
pub fn handle_connection(state: ServerState, conn: Connection) {
// WebSocket接続の確立
let client = ClientInfo(
id: generate_client_id(),
connection: conn,
)
// メッセージハンドラーの設定
conn
|> websocket.on_message(fn(msg) {
handle_sync_message(state, client, msg)
})
}
fn handle_sync_message(state: ServerState, client: ClientInfo, msg: Message) {
case msg {
// Automerge同期メッセージの処理
SyncMessage(changes) -> {
let doc = get_document(state, client.doc_id)
let updated_doc = automerge.apply_changes(doc, changes)
// 変更を他のクライアントに配信
broadcast_changes(state, client.doc_id, changes)
// 永続化
persist_document(updated_doc)
}
// その他のメッセージ処理
_ -> Ok(Nil)
}
}
// IndexedDBストレージアダプターの設定
const storage = new IndexedDBStorageAdapter()
// カスタム永続化ロジック
class CustomStorage extends IndexedDBStorageAdapter {
async save(docUrl: AutomergeUrl, doc: Automerge.Doc<any>) {
// 変更をローカルに保存
await super.save(docUrl, doc)
// メタデータの更新
await this.saveMetadata(docUrl, {
lastSaved: Date.now(),
changeCount: doc.getHistory().length
})
}
}
// オフライン検知と再接続
const setupOfflineSupport = (repo: Automerge.Repo) => {
window.addEventListener('offline', () => {
repo.networkSubsystem.pause()
})
window.addEventListener('online', async () => {
repo.networkSubsystem.resume()
await repo.sync()
})
}
// 同期マネージャー
class SyncManager {
constructor(
private repo: Automerge.Repo,
private storage: CustomStorage
) {}
async syncChanges() {
const pendingDocs = await this.storage.getPendingDocs()
for (const docUrl of pendingDocs) {
const doc = await this.repo.find(docUrl)
if (doc) {
await this.repo.sync(docUrl)
}
}
}
async handleConflicts(docUrl: AutomergeUrl) {
const doc = await this.repo.find(docUrl)
if (doc) {
// Automergeは自動的にコンフリクトを解決
// 必要に応じてカスタムの解決ロジックを追加
const conflicts = doc.getConflicts()
if (conflicts.length > 0) {
this.logConflictResolution(conflicts)
}
}
}
}
// サーバーサイドの実装
import { Automerge } from '@automerge/automerge'
import { AutomergeServer } from '@automerge/automerge-server'
const server = new AutomergeServer({
// Redis永続化アダプター
storage: new RedisStorageAdapter({
url: process.env.REDIS_URL,
}),
// カスタムロギング
logger: {
info: (msg) => console.log(msg),
error: (msg) => console.error(msg),
},
// 認証ハンドラー
authenticate: async (request) => {
const token = request.headers.authorization
return validateToken(token)
},
})
// クライアント接続の処理
server.on('connection', (client) => {
console.log(`Client connected: ${client.id}`)
client.on('sync', async (docId) => {
const doc = await loadDocument(docId)
client.send(doc)
})
})
// 変更のバッチ処理
const batchChanges = (changes: Automerge.Change[]) => {
return Automerge.Change.squash(changes)
}
// メモリ使用量の最適化
const optimizeMemory = (doc: Automerge.Doc<any>) => {
return Automerge.compact(doc)
}
// 大規模ドキュメントの処理
const handleLargeDoc = async (docUrl: AutomergeUrl) => {
const doc = await repo.find(docUrl)
if (doc) {
// 変更履歴の圧縮
const compacted = Automerge.compact(doc)
// チャンク分割による効率的な同期
const chunks = Automerge.save(compacted)
for (const chunk of chunks) {
await repo.saveChunk(docUrl, chunk)
}
}
}
// Automergeメトリクスコレクター
class MetricsCollector {
collect(doc: Automerge.Doc<any>) {
return {
changeCount: doc.getHistory().length,
byteSize: Automerge.save(doc).length,
actors: doc.getActors().length,
conflicts: doc.getConflicts().length
}
}
}
// Prometheusエクスポーター
const exportMetrics = (metrics: DocMetrics) => {
prometheus.gauge({
name: 'automerge_doc_size_bytes',
help: 'Document size in bytes',
value: metrics.byteSize
})
prometheus.counter({
name: 'automerge_changes_total',
help: 'Total number of changes',
value: metrics.changeCount
})
}
// デバッグモードの有効化
const enableDebugMode = (repo: Automerge.Repo) => {
repo.registerDebugHandler((event) => {
console.log('[Automerge Debug]', {
type: event.type,
docId: event.docId,
changeset: event.changes?.length,
timestamp: Date.now()
})
})
}
// 変更履歴の検査
const inspectHistory = (doc: Automerge.Doc<any>) => {
const history = doc.getHistory()
return history.map(change => ({
actor: change.actor,
timestamp: change.timestamp,
deps: change.deps,
operations: change.operations
}))
}
// カスタム認証インテグレーション
const setupSecureRepo = (config: SecurityConfig) => {
return new Automerge.Repo({
network: [
new SecureNetworkAdapter({
validateToken: config.validateToken,
encryptChanges: config.encryptChanges
})
],
storage: new EncryptedStorageAdapter({
key: config.storageKey
})
})
}
// 変更の検証
const validateChange = (change: Automerge.Change) => {
// アクターの検証
if (!isValidActor(change.actor)) {
throw new Error('Invalid actor')
}
// 操作の検証
for (const op of change.operations) {
if (!isAllowedOperation(op)) {
throw new Error('Operation not allowed')
}
}
}