From a1383a9304ff457d6671e12ded4265b135256004 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 31 Dec 2025 15:25:36 +0900 Subject: feat(crdt): add crdtChanges to sync push payload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add CRDT document generation to the sync push flow. Each pending entity is now converted to an Automerge CRDT document and included as base64- encoded binary in the push payload alongside the existing entity data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/client/sync/push.ts | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) (limited to 'src/client/sync/push.ts') diff --git a/src/client/sync/push.ts b/src/client/sync/push.ts index b83136e..eea671b 100644 --- a/src/client/sync/push.ts +++ b/src/client/sync/push.ts @@ -7,6 +7,17 @@ import type { LocalNoteType, LocalReviewLog, } from "../db/index"; +import { + crdtCardRepository, + crdtDeckRepository, + crdtNoteFieldTypeRepository, + crdtNoteFieldValueRepository, + crdtNoteRepository, + crdtNoteTypeRepository, + crdtReviewLogRepository, +} from "./crdt"; +import type { CrdtSyncPayload } from "./crdt/sync-state"; +import { binaryToBase64 } from "./crdt/sync-state"; import type { PendingChanges, SyncQueue } from "./queue"; /** @@ -20,6 +31,8 @@ export interface SyncPushData { noteFieldTypes: SyncNoteFieldTypeData[]; notes: SyncNoteData[]; noteFieldValues: SyncNoteFieldValueData[]; + /** CRDT document changes for conflict-free sync */ + crdtChanges: CrdtSyncPayload[]; } export interface SyncDeckData { @@ -254,6 +267,94 @@ function noteFieldValueToSyncData( }; } +/** + * Generate CRDT sync payloads from pending changes + */ +export function generateCrdtChanges( + changes: PendingChanges, +): CrdtSyncPayload[] { + const crdtChanges: CrdtSyncPayload[] = []; + + // Convert decks to CRDT documents + for (const deck of changes.decks) { + const result = crdtDeckRepository.toCrdtDocument(deck); + crdtChanges.push({ + documentId: result.documentId, + entityType: crdtDeckRepository.entityType, + entityId: deck.id, + binary: binaryToBase64(result.binary), + }); + } + + // Convert note types to CRDT documents + for (const noteType of changes.noteTypes) { + const result = crdtNoteTypeRepository.toCrdtDocument(noteType); + crdtChanges.push({ + documentId: result.documentId, + entityType: crdtNoteTypeRepository.entityType, + entityId: noteType.id, + binary: binaryToBase64(result.binary), + }); + } + + // Convert note field types to CRDT documents + for (const fieldType of changes.noteFieldTypes) { + const result = crdtNoteFieldTypeRepository.toCrdtDocument(fieldType); + crdtChanges.push({ + documentId: result.documentId, + entityType: crdtNoteFieldTypeRepository.entityType, + entityId: fieldType.id, + binary: binaryToBase64(result.binary), + }); + } + + // Convert notes to CRDT documents + for (const note of changes.notes) { + const result = crdtNoteRepository.toCrdtDocument(note); + crdtChanges.push({ + documentId: result.documentId, + entityType: crdtNoteRepository.entityType, + entityId: note.id, + binary: binaryToBase64(result.binary), + }); + } + + // Convert note field values to CRDT documents + for (const fieldValue of changes.noteFieldValues) { + const result = crdtNoteFieldValueRepository.toCrdtDocument(fieldValue); + crdtChanges.push({ + documentId: result.documentId, + entityType: crdtNoteFieldValueRepository.entityType, + entityId: fieldValue.id, + binary: binaryToBase64(result.binary), + }); + } + + // Convert cards to CRDT documents + for (const card of changes.cards) { + const result = crdtCardRepository.toCrdtDocument(card); + crdtChanges.push({ + documentId: result.documentId, + entityType: crdtCardRepository.entityType, + entityId: card.id, + binary: binaryToBase64(result.binary), + }); + } + + // Convert review logs to CRDT documents + for (const reviewLog of changes.reviewLogs) { + const result = crdtReviewLogRepository.toCrdtDocument(reviewLog); + crdtChanges.push({ + documentId: result.documentId, + entityType: crdtReviewLogRepository.entityType, + entityId: reviewLog.id, + binary: binaryToBase64(result.binary), + }); + } + + return crdtChanges; +} + /** * Convert pending changes to sync push data format */ @@ -268,6 +369,7 @@ export function pendingChangesToPushData( noteFieldTypes: changes.noteFieldTypes.map(noteFieldTypeToSyncData), notes: changes.notes.map(noteToSyncData), noteFieldValues: changes.noteFieldValues.map(noteFieldValueToSyncData), + crdtChanges: generateCrdtChanges(changes), }; } -- cgit v1.2.3-70-g09d2