瀏覽代碼

bridge flutter delta to rust delta

appflowy 3 年之前
父節點
當前提交
335b5327de
共有 80 個文件被更改,包括 1381 次插入633 次删除
  1. 10 3
      app_flowy/lib/workspace/application/doc/doc_bloc.dart
  2. 5 3
      app_flowy/lib/workspace/application/doc/doc_edit_bloc.dart
  3. 17 5
      app_flowy/lib/workspace/domain/i_doc.dart
  4. 15 7
      app_flowy/lib/workspace/infrastructure/i_doc_impl.dart
  5. 12 6
      app_flowy/lib/workspace/infrastructure/repos/doc_repo.dart
  6. 5 4
      app_flowy/lib/workspace/presentation/doc/doc_page.dart
  7. 8 1
      app_flowy/packages/flowy_editor/lib/src/model/document/document.dart
  8. 6 12
      app_flowy/packages/flowy_editor/lib/src/service/controller.dart
  9. 21 4
      app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart
  10. 84 36
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/doc.pb.dart
  11. 20 12
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/doc.pbjson.dart
  12. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbenum.dart
  13. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbjson.dart
  14. 4 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart
  15. 3 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart
  16. 4 4
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pb.dart
  17. 2 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pbjson.dart
  18. 78 17
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_update.pb.dart
  19. 17 6
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_update.pbjson.dart
  20. 1 1
      backend/migrations/20210909115140_doc.sql
  21. 1 1
      backend/src/entities/doc.rs
  22. 3 6
      backend/src/service/doc_service/doc.rs
  23. 2 2
      backend/src/service/doc_service/router.rs
  24. 2 5
      backend/src/service/doc_service/sql_builder.rs
  25. 6 1
      backend/src/service/workspace_service/user_default/user_default.rs
  26. 6 9
      backend/tests/api/helper.rs
  27. 2 0
      rust-lib/dart-ffi/src/protobuf/mod.rs
  28. 5 5
      rust-lib/dart-ffi/src/protobuf/model/mod.rs
  29. 1 1
      rust-lib/flowy-database/migrations/2021-07-22-234458_flowy-editor/up.sql
  30. 8 2
      rust-lib/flowy-database/src/schema.rs
  31. 8 5
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  32. 15 11
      rust-lib/flowy-document/src/entities/doc/doc.rs
  33. 4 0
      rust-lib/flowy-document/src/errors.rs
  34. 24 11
      rust-lib/flowy-document/src/module.rs
  35. 2 0
      rust-lib/flowy-document/src/protobuf/mod.rs
  36. 297 129
      rust-lib/flowy-document/src/protobuf/model/doc.rs
  37. 9 9
      rust-lib/flowy-document/src/protobuf/model/mod.rs
  38. 8 4
      rust-lib/flowy-document/src/protobuf/proto/doc.proto
  39. 10 5
      rust-lib/flowy-document/src/services/doc_cache.rs
  40. 7 7
      rust-lib/flowy-document/src/services/doc_controller.rs
  41. 0 3
      rust-lib/flowy-document/src/services/doc_manager/mod.rs
  42. 2 2
      rust-lib/flowy-document/src/services/mod.rs
  43. 2 2
      rust-lib/flowy-document/src/services/server/mod.rs
  44. 3 3
      rust-lib/flowy-document/src/services/server/server_api.rs
  45. 2 2
      rust-lib/flowy-document/src/services/server/server_api_mock.rs
  46. 4 4
      rust-lib/flowy-document/src/sql_tables/doc/doc_table.rs
  47. 3 0
      rust-lib/flowy-infra/src/lib.rs
  48. 2 0
      rust-lib/flowy-infra/src/protobuf/mod.rs
  49. 3 3
      rust-lib/flowy-infra/src/protobuf/model/mod.rs
  50. 1 1
      rust-lib/flowy-log/src/lib.rs
  51. 2 0
      rust-lib/flowy-observable/src/protobuf/mod.rs
  52. 3 3
      rust-lib/flowy-observable/src/protobuf/model/mod.rs
  53. 1 0
      rust-lib/flowy-ot/Cargo.toml
  54. 12 0
      rust-lib/flowy-ot/src/client/document/document.rs
  55. 24 3
      rust-lib/flowy-ot/src/core/delta/delta.rs
  56. 5 1
      rust-lib/flowy-ot/src/errors.rs
  57. 2 0
      rust-lib/flowy-user/src/protobuf/mod.rs
  58. 13 13
      rust-lib/flowy-user/src/protobuf/model/mod.rs
  59. 1 0
      rust-lib/flowy-workspace/Cargo.toml
  60. 16 0
      rust-lib/flowy-workspace/src/entities/view/parser/delta_data.rs
  61. 2 0
      rust-lib/flowy-workspace/src/entities/view/parser/mod.rs
  62. 18 10
      rust-lib/flowy-workspace/src/entities/view/view_create.rs
  63. 40 9
      rust-lib/flowy-workspace/src/entities/view/view_update.rs
  64. 3 0
      rust-lib/flowy-workspace/src/errors.rs
  65. 5 2
      rust-lib/flowy-workspace/src/event.rs
  66. 15 4
      rust-lib/flowy-workspace/src/handlers/view_handler.rs
  67. 2 1
      rust-lib/flowy-workspace/src/module.rs
  68. 2 0
      rust-lib/flowy-workspace/src/protobuf/mod.rs
  69. 51 45
      rust-lib/flowy-workspace/src/protobuf/model/errors.rs
  70. 45 40
      rust-lib/flowy-workspace/src/protobuf/model/event.rs
  71. 33 33
      rust-lib/flowy-workspace/src/protobuf/model/mod.rs
  72. 15 15
      rust-lib/flowy-workspace/src/protobuf/model/view_create.rs
  73. 290 80
      rust-lib/flowy-workspace/src/protobuf/model/view_update.rs
  74. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/errors.proto
  75. 2 1
      rust-lib/flowy-workspace/src/protobuf/proto/event.proto
  76. 1 1
      rust-lib/flowy-workspace/src/protobuf/proto/view_create.proto
  77. 6 2
      rust-lib/flowy-workspace/src/protobuf/proto/view_update.proto
  78. 9 5
      rust-lib/flowy-workspace/src/services/view_controller.rs
  79. 2 2
      rust-lib/flowy-workspace/tests/workspace/helper.rs
  80. 2 2
      rust-lib/flowy-workspace/tests/workspace/view_test.rs

+ 10 - 3
app_flowy/lib/workspace/application/doc/doc_bloc.dart

@@ -1,3 +1,5 @@
+import 'dart:typed_data';
+
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/workspace/domain/i_doc.dart';
@@ -26,7 +28,12 @@ class DocBloc extends Bloc<DocEvent, DocState> {
     final docOrFail = await iDocImpl.readDoc();
     yield docOrFail.fold(
       (doc) {
-        final flowyDoc = FlowyDoc(doc: doc, data: _decodeToDocument(doc.data));
+        final flowyDoc = FlowyDoc(
+            doc: doc,
+            data: _decodeToDocument(
+              Uint8List.fromList(doc.data),
+            ),
+            iDocImpl: iDocImpl);
         return DocState.loadDoc(flowyDoc);
       },
       (error) {
@@ -35,8 +42,8 @@ class DocBloc extends Bloc<DocEvent, DocState> {
     );
   }
 
-  Document _decodeToDocument(String text) {
-    final json = jsonDecode(text);
+  Document _decodeToDocument(Uint8List data) {
+    final json = jsonDecode(utf8.decode(data));
     final document = Document.fromJson(json);
     return document;
   }

+ 5 - 3
app_flowy/lib/workspace/application/doc/doc_edit_bloc.dart

@@ -13,12 +13,14 @@ class DocEditBloc extends Bloc<DocEditEvent, DocEditState> {
   Stream<DocEditState> mapEventToState(DocEditEvent event) async* {
     yield* event.map(
         initial: (e) async* {},
-        close: (Close value) async* {},
+        close: (Close value) async* {
+          iDocImpl.closeDoc();
+        },
         changeset: (Changeset changeset) async* {
-          iDocImpl.updateWithChangeset(text: changeset.data);
+          iDocImpl.applyChangeset(json: changeset.data);
         },
         save: (Save save) async* {
-          iDocImpl.saveDoc(text: save.data);
+          iDocImpl.saveDoc(json: save.data);
         });
   }
 }

+ 17 - 5
app_flowy/lib/workspace/domain/i_doc.dart

@@ -1,19 +1,31 @@
+import 'dart:convert';
+
 import 'package:flowy_editor/flowy_editor.dart';
 import 'package:dartz/dartz.dart';
+import 'package:flowy_editor/src/model/quill_delta.dart';
 import 'package:flowy_sdk/protobuf/flowy-document/doc.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 
-class FlowyDoc {
+class FlowyDoc implements EditorChangesetSender {
   final Doc doc;
-  final Document data;
+  final IDoc iDocImpl;
+  Document data;
 
-  FlowyDoc({required this.doc, required this.data});
+  FlowyDoc({required this.doc, required this.data, required this.iDocImpl}) {
+    data.sender = this;
+  }
   String get id => doc.id;
+
+  @override
+  void sendDelta(Delta delta) {
+    final json = jsonEncode(delta.toJson());
+    iDocImpl.applyChangeset(json: json);
+  }
 }
 
 abstract class IDoc {
   Future<Either<Doc, WorkspaceError>> readDoc();
-  Future<Either<Unit, WorkspaceError>> saveDoc({String? text});
-  Future<Either<Unit, WorkspaceError>> updateWithChangeset({String? text});
+  Future<Either<Unit, WorkspaceError>> saveDoc({String? json});
+  Future<Either<Unit, WorkspaceError>> applyChangeset({String? json});
   Future<Either<Unit, WorkspaceError>> closeDoc();
 }

+ 15 - 7
app_flowy/lib/workspace/infrastructure/i_doc_impl.dart

@@ -1,9 +1,11 @@
 import 'dart:convert';
+import 'dart:typed_data';
 
 import 'package:dartz/dartz.dart';
 import 'package:flowy_editor/flowy_editor.dart';
 import 'package:app_flowy/workspace/domain/i_doc.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/doc_repo.dart';
+import 'package:flowy_editor/src/model/quill_delta.dart';
 import 'package:flowy_log/flowy_log.dart';
 import 'package:flowy_sdk/protobuf/flowy-document/doc.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
@@ -25,19 +27,23 @@ class IDocImpl extends IDoc {
   }
 
   @override
-  Future<Either<Unit, WorkspaceError>> saveDoc({String? text}) {
+  Future<Either<Unit, WorkspaceError>> saveDoc({String? json}) {
     Log.debug("Saving doc");
-    final json = jsonEncode(text ?? "");
-    return repo.updateDoc(text: json);
+    return repo.saveDoc(data: _encodeText(json));
   }
 
   @override
-  Future<Either<Unit, WorkspaceError>> updateWithChangeset({String? text}) {
-    return repo.updateWithChangeset(text: text);
+  Future<Either<Unit, WorkspaceError>> applyChangeset({String? json}) {
+    return repo.applyChangeset(data: _encodeText(json));
   }
 }
 
-class EditorPersistenceImpl extends EditorPersistence {
+Uint8List _encodeText(String? json) {
+  final data = utf8.encode(json ?? "");
+  return Uint8List.fromList(data);
+}
+
+class EditorPersistenceImpl implements EditorPersistence {
   DocRepository repo;
   EditorPersistenceImpl({
     required this.repo,
@@ -47,7 +53,9 @@ class EditorPersistenceImpl extends EditorPersistence {
   Future<bool> save(List<dynamic> jsonList) async {
     Log.debug("Saving doc");
     final json = jsonEncode(jsonList);
-    return repo.updateDoc(text: json).then((result) {
+    final data = utf8.encode(json);
+
+    return repo.saveDoc(data: Uint8List.fromList(data)).then((result) {
       return result.fold(
         (l) => true,
         (r) => false,

+ 12 - 6
app_flowy/lib/workspace/infrastructure/repos/doc_repo.dart

@@ -1,3 +1,5 @@
+import 'dart:typed_data';
+
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-document/doc.pb.dart';
@@ -16,15 +18,19 @@ class DocRepository {
     return WorkspaceEventOpenView(request).send();
   }
 
-  Future<Either<Unit, WorkspaceError>> updateDoc({String? text}) {
-    final request = UpdateViewDataRequest.create()
+  Future<Either<Unit, WorkspaceError>> saveDoc({required Uint8List data}) {
+    final request = SaveViewDataRequest.create()
       ..viewId = docId
-      ..data = text ?? "";
-    return WorkspaceEventUpdateViewData(request).send();
+      ..data = data;
+    return WorkspaceEventSaveViewData(request).send();
   }
 
-  Future<Either<Unit, WorkspaceError>> updateWithChangeset({String? text}) {
-    throw UnimplementedError();
+  Future<Either<Unit, WorkspaceError>> applyChangeset(
+      {required Uint8List data}) {
+    final request = ApplyChangesetRequest.create()
+      ..viewId = docId
+      ..data = data;
+    return WorkspaceEventApplyChangeset(request).send();
   }
 
   Future<Either<Unit, WorkspaceError>> closeDoc(

+ 5 - 4
app_flowy/lib/workspace/presentation/doc/doc_page.dart

@@ -14,11 +14,12 @@ class DocPage extends StatelessWidget {
   final FlowyDoc doc;
 
   DocPage({Key? key, required this.doc}) : super(key: key) {
+    // getIt<EditorChangesetSender>(param1: doc.id))
+
     controller = EditorController(
-      document: doc.data,
-      selection: const TextSelection.collapsed(offset: 0),
-      persistence: getIt<EditorPersistence>(param1: doc.id),
-    );
+        document: doc.data,
+        selection: const TextSelection.collapsed(offset: 0),
+        persistence: getIt<EditorPersistence>(param1: doc.id));
   }
 
   @override

+ 8 - 1
app_flowy/packages/flowy_editor/lib/src/model/document/document.dart

@@ -14,9 +14,15 @@ import 'node/line.dart';
 import 'node/node.dart';
 import 'package:flowy_log/flowy_log.dart';
 
+abstract class EditorChangesetSender {
+  void sendDelta(Delta delta);
+}
+
 /// The rich text document
 class Document {
-  Document() : _delta = Delta()..insert('\n') {
+  EditorChangesetSender? sender;
+
+  Document({this.sender}) : _delta = Delta()..insert('\n') {
     _loadDocument(_delta);
   }
 
@@ -176,6 +182,7 @@ class Document {
     }
 
     try {
+      sender?.sendDelta(delta);
       _delta = _delta.compose(delta);
     } catch (e) {
       throw '_delta compose failed';

+ 6 - 12
app_flowy/packages/flowy_editor/lib/src/service/controller.dart

@@ -15,24 +15,17 @@ abstract class EditorPersistence {
 }
 
 class EditorController extends ChangeNotifier {
+  final Document document;
+  TextSelection selection;
   final EditorPersistence? persistence;
+  Style toggledStyle = Style();
+
   EditorController({
     required this.document,
     required this.selection,
     this.persistence,
   });
 
-  factory EditorController.basic() {
-    return EditorController(
-      document: Document(),
-      selection: const TextSelection.collapsed(offset: 0),
-    );
-  }
-
-  final Document document;
-  TextSelection selection;
-  Style toggledStyle = Style();
-
   // item1: Document state before [change].
   // item2: Change delta applied to the document.
   // item3: The source of this change.
@@ -67,7 +60,6 @@ class EditorController extends ChangeNotifier {
 
   void save() {
     if (persistence != null) {
-      final a = document.toPlainText();
       persistence!.save(document.toDelta().toJson());
     }
   }
@@ -97,6 +89,7 @@ class EditorController extends ChangeNotifier {
     // final change =
     //     document.format(index, length, LinkAttribute("www.baidu.com"));
     final change = document.format(index, length, attribute);
+
     final adjustedSelection = selection.copyWith(
       baseOffset: change.transformPosition(selection.baseOffset),
       extentOffset: change.transformPosition(selection.extentOffset),
@@ -114,6 +107,7 @@ class EditorController extends ChangeNotifier {
     Delta? delta;
     if (length > 0 || data is! String || data.isNotEmpty) {
       delta = document.replace(index, length, data);
+
       var shouldRetainDelta = toggledStyle.isNotEmpty &&
           delta.isNotEmpty &&
           delta.length <= 2 &&

+ 21 - 4
app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart

@@ -254,13 +254,30 @@ class WorkspaceEventOpenView {
     }
 }
 
-class WorkspaceEventUpdateViewData {
-     UpdateViewDataRequest request;
-     WorkspaceEventUpdateViewData(this.request);
+class WorkspaceEventSaveViewData {
+     SaveViewDataRequest request;
+     WorkspaceEventSaveViewData(this.request);
 
     Future<Either<Unit, WorkspaceError>> send() {
     final request = FFIRequest.create()
-          ..event = WorkspaceEvent.UpdateViewData.toString()
+          ..event = WorkspaceEvent.SaveViewData.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class WorkspaceEventApplyChangeset {
+     ApplyChangesetRequest request;
+     WorkspaceEventApplyChangeset(this.request);
+
+    Future<Either<Unit, WorkspaceError>> send() {
+    final request = FFIRequest.create()
+          ..event = WorkspaceEvent.ApplyChangeset.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)

+ 84 - 36
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/doc.pb.dart

@@ -12,14 +12,14 @@ import 'package:protobuf/protobuf.dart' as $pb;
 class CreateDocParams extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateDocParams', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
+    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
     ..hasRequiredFields = false
   ;
 
   CreateDocParams._() : super();
   factory CreateDocParams({
     $core.String? id,
-    $core.String? data,
+    $core.List<$core.int>? data,
   }) {
     final _result = create();
     if (id != null) {
@@ -61,9 +61,9 @@ class CreateDocParams extends $pb.GeneratedMessage {
   void clearId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.String get data => $_getSZ(1);
+  $core.List<$core.int> get data => $_getN(1);
   @$pb.TagNumber(2)
-  set data($core.String v) { $_setString(1, v); }
+  set data($core.List<$core.int> v) { $_setBytes(1, v); }
   @$pb.TagNumber(2)
   $core.bool hasData() => $_has(1);
   @$pb.TagNumber(2)
@@ -73,14 +73,14 @@ class CreateDocParams extends $pb.GeneratedMessage {
 class Doc extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Doc', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
+    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
     ..hasRequiredFields = false
   ;
 
   Doc._() : super();
   factory Doc({
     $core.String? id,
-    $core.String? data,
+    $core.List<$core.int>? data,
   }) {
     final _result = create();
     if (id != null) {
@@ -122,36 +122,87 @@ class Doc extends $pb.GeneratedMessage {
   void clearId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.String get data => $_getSZ(1);
+  $core.List<$core.int> get data => $_getN(1);
   @$pb.TagNumber(2)
-  set data($core.String v) { $_setString(1, v); }
+  set data($core.List<$core.int> v) { $_setBytes(1, v); }
   @$pb.TagNumber(2)
   $core.bool hasData() => $_has(1);
   @$pb.TagNumber(2)
   void clearData() => clearField(2);
 }
 
-enum UpdateDocParams_OneOfData {
-  data, 
-  notSet
+class SaveDocParams extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SaveDocParams', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false
+  ;
+
+  SaveDocParams._() : super();
+  factory SaveDocParams({
+    $core.String? id,
+    $core.List<$core.int>? data,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (data != null) {
+      _result.data = data;
+    }
+    return _result;
+  }
+  factory SaveDocParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SaveDocParams.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  SaveDocParams clone() => SaveDocParams()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  SaveDocParams copyWith(void Function(SaveDocParams) updates) => super.copyWith((message) => updates(message as SaveDocParams)) as SaveDocParams; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static SaveDocParams create() => SaveDocParams._();
+  SaveDocParams createEmptyInstance() => create();
+  static $pb.PbList<SaveDocParams> createRepeated() => $pb.PbList<SaveDocParams>();
+  @$core.pragma('dart2js:noInline')
+  static SaveDocParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SaveDocParams>(create);
+  static SaveDocParams? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get id => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set id($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.List<$core.int> get data => $_getN(1);
+  @$pb.TagNumber(2)
+  set data($core.List<$core.int> v) { $_setBytes(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasData() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearData() => clearField(2);
 }
 
-class UpdateDocParams extends $pb.GeneratedMessage {
-  static const $core.Map<$core.int, UpdateDocParams_OneOfData> _UpdateDocParams_OneOfDataByTag = {
-    2 : UpdateDocParams_OneOfData.data,
-    0 : UpdateDocParams_OneOfData.notSet
-  };
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UpdateDocParams', createEmptyInstance: create)
-    ..oo(0, [2])
+class ApplyChangesetParams extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ApplyChangesetParams', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
+    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
     ..hasRequiredFields = false
   ;
 
-  UpdateDocParams._() : super();
-  factory UpdateDocParams({
+  ApplyChangesetParams._() : super();
+  factory ApplyChangesetParams({
     $core.String? id,
-    $core.String? data,
+    $core.List<$core.int>? data,
   }) {
     final _result = create();
     if (id != null) {
@@ -162,29 +213,26 @@ class UpdateDocParams extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory UpdateDocParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory UpdateDocParams.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory ApplyChangesetParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory ApplyChangesetParams.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  UpdateDocParams clone() => UpdateDocParams()..mergeFromMessage(this);
+  ApplyChangesetParams clone() => ApplyChangesetParams()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  UpdateDocParams copyWith(void Function(UpdateDocParams) updates) => super.copyWith((message) => updates(message as UpdateDocParams)) as UpdateDocParams; // ignore: deprecated_member_use
+  ApplyChangesetParams copyWith(void Function(ApplyChangesetParams) updates) => super.copyWith((message) => updates(message as ApplyChangesetParams)) as ApplyChangesetParams; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static UpdateDocParams create() => UpdateDocParams._();
-  UpdateDocParams createEmptyInstance() => create();
-  static $pb.PbList<UpdateDocParams> createRepeated() => $pb.PbList<UpdateDocParams>();
+  static ApplyChangesetParams create() => ApplyChangesetParams._();
+  ApplyChangesetParams createEmptyInstance() => create();
+  static $pb.PbList<ApplyChangesetParams> createRepeated() => $pb.PbList<ApplyChangesetParams>();
   @$core.pragma('dart2js:noInline')
-  static UpdateDocParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UpdateDocParams>(create);
-  static UpdateDocParams? _defaultInstance;
-
-  UpdateDocParams_OneOfData whichOneOfData() => _UpdateDocParams_OneOfDataByTag[$_whichOneof(0)]!;
-  void clearOneOfData() => clearField($_whichOneof(0));
+  static ApplyChangesetParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplyChangesetParams>(create);
+  static ApplyChangesetParams? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get id => $_getSZ(0);
@@ -196,9 +244,9 @@ class UpdateDocParams extends $pb.GeneratedMessage {
   void clearId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.String get data => $_getSZ(1);
+  $core.List<$core.int> get data => $_getN(1);
   @$pb.TagNumber(2)
-  set data($core.String v) { $_setString(1, v); }
+  set data($core.List<$core.int> v) { $_setBytes(1, v); }
   @$pb.TagNumber(2)
   $core.bool hasData() => $_has(1);
   @$pb.TagNumber(2)

+ 20 - 12
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/doc.pbjson.dart

@@ -13,37 +13,45 @@ const CreateDocParams$json = const {
   '1': 'CreateDocParams',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
-    const {'1': 'data', '3': 2, '4': 1, '5': 9, '10': 'data'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'},
   ],
 };
 
 /// Descriptor for `CreateDocParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List createDocParamsDescriptor = $convert.base64Decode('Cg9DcmVhdGVEb2NQYXJhbXMSDgoCaWQYASABKAlSAmlkEhIKBGRhdGEYAiABKAlSBGRhdGE=');
+final $typed_data.Uint8List createDocParamsDescriptor = $convert.base64Decode('Cg9DcmVhdGVEb2NQYXJhbXMSDgoCaWQYASABKAlSAmlkEhIKBGRhdGEYAiABKAxSBGRhdGE=');
 @$core.Deprecated('Use docDescriptor instead')
 const Doc$json = const {
   '1': 'Doc',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
-    const {'1': 'data', '3': 2, '4': 1, '5': 9, '10': 'data'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'},
   ],
 };
 
 /// Descriptor for `Doc`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List docDescriptor = $convert.base64Decode('CgNEb2MSDgoCaWQYASABKAlSAmlkEhIKBGRhdGEYAiABKAlSBGRhdGE=');
-@$core.Deprecated('Use updateDocParamsDescriptor instead')
-const UpdateDocParams$json = const {
-  '1': 'UpdateDocParams',
+final $typed_data.Uint8List docDescriptor = $convert.base64Decode('CgNEb2MSDgoCaWQYASABKAlSAmlkEhIKBGRhdGEYAiABKAxSBGRhdGE=');
+@$core.Deprecated('Use saveDocParamsDescriptor instead')
+const SaveDocParams$json = const {
+  '1': 'SaveDocParams',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
-    const {'1': 'data', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'data'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'},
   ],
-  '8': const [
-    const {'1': 'one_of_data'},
+};
+
+/// Descriptor for `SaveDocParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List saveDocParamsDescriptor = $convert.base64Decode('Cg1TYXZlRG9jUGFyYW1zEg4KAmlkGAEgASgJUgJpZBISCgRkYXRhGAIgASgMUgRkYXRh');
+@$core.Deprecated('Use applyChangesetParamsDescriptor instead')
+const ApplyChangesetParams$json = const {
+  '1': 'ApplyChangesetParams',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'},
   ],
 };
 
-/// Descriptor for `UpdateDocParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List updateDocParamsDescriptor = $convert.base64Decode('Cg9VcGRhdGVEb2NQYXJhbXMSDgoCaWQYASABKAlSAmlkEhQKBGRhdGEYAiABKAlIAFIEZGF0YUINCgtvbmVfb2ZfZGF0YQ==');
+/// Descriptor for `ApplyChangesetParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List applyChangesetParamsDescriptor = $convert.base64Decode('ChRBcHBseUNoYW5nZXNldFBhcmFtcxIOCgJpZBgBIAEoCVICaWQSEgoEZGF0YRgCIAEoDFIEZGF0YQ==');
 @$core.Deprecated('Use queryDocParamsDescriptor instead')
 const QueryDocParams$json = const {
   '1': 'QueryDocParams',

+ 2 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbenum.dart

@@ -22,6 +22,7 @@ class ErrorCode extends $pb.ProtobufEnum {
   static const ErrorCode ViewThumbnailInvalid = ErrorCode._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewThumbnailInvalid');
   static const ErrorCode ViewIdInvalid = ErrorCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewIdInvalid');
   static const ErrorCode ViewDescInvalid = ErrorCode._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewDescInvalid');
+  static const ErrorCode ViewDataInvalid = ErrorCode._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewDataInvalid');
   static const ErrorCode UserIdIsEmpty = ErrorCode._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdIsEmpty');
   static const ErrorCode UserUnauthorized = ErrorCode._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
   static const ErrorCode InternalError = ErrorCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InternalError');
@@ -40,6 +41,7 @@ class ErrorCode extends $pb.ProtobufEnum {
     ViewThumbnailInvalid,
     ViewIdInvalid,
     ViewDescInvalid,
+    ViewDataInvalid,
     UserIdIsEmpty,
     UserUnauthorized,
     InternalError,

+ 2 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbjson.dart

@@ -24,6 +24,7 @@ const ErrorCode$json = const {
     const {'1': 'ViewThumbnailInvalid', '2': 21},
     const {'1': 'ViewIdInvalid', '2': 22},
     const {'1': 'ViewDescInvalid', '2': 23},
+    const {'1': 'ViewDataInvalid', '2': 24},
     const {'1': 'UserIdIsEmpty', '2': 100},
     const {'1': 'UserUnauthorized', '2': 101},
     const {'1': 'InternalError', '2': 1000},
@@ -32,7 +33,7 @@ const ErrorCode$json = const {
 };
 
 /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIYChRXb3Jrc3BhY2VEZXNjSW52YWxpZBAEEhwKGEN1cnJlbnRXb3Jrc3BhY2VOb3RGb3VuZBAFEhAKDEFwcElkSW52YWxpZBAKEhIKDkFwcE5hbWVJbnZhbGlkEAsSEwoPVmlld05hbWVJbnZhbGlkEBQSGAoUVmlld1RodW1ibmFpbEludmFsaWQQFRIRCg1WaWV3SWRJbnZhbGlkEBYSEwoPVmlld0Rlc2NJbnZhbGlkEBcSEQoNVXNlcklkSXNFbXB0eRBkEhQKEFVzZXJVbmF1dGhvcml6ZWQQZRISCg1JbnRlcm5hbEVycm9yEOgHEhMKDlJlY29yZE5vdEZvdW5kEOkH');
+final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIYChRXb3Jrc3BhY2VEZXNjSW52YWxpZBAEEhwKGEN1cnJlbnRXb3Jrc3BhY2VOb3RGb3VuZBAFEhAKDEFwcElkSW52YWxpZBAKEhIKDkFwcE5hbWVJbnZhbGlkEAsSEwoPVmlld05hbWVJbnZhbGlkEBQSGAoUVmlld1RodW1ibmFpbEludmFsaWQQFRIRCg1WaWV3SWRJbnZhbGlkEBYSEwoPVmlld0Rlc2NJbnZhbGlkEBcSEwoPVmlld0RhdGFJbnZhbGlkEBgSEQoNVXNlcklkSXNFbXB0eRBkEhQKEFVzZXJVbmF1dGhvcml6ZWQQZRISCg1JbnRlcm5hbEVycm9yEOgHEhMKDlJlY29yZE5vdEZvdW5kEOkH');
 @$core.Deprecated('Use workspaceErrorDescriptor instead')
 const WorkspaceError$json = const {
   '1': 'WorkspaceError',

+ 4 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart

@@ -25,7 +25,8 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
   static const WorkspaceEvent UpdateView = WorkspaceEvent._(203, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateView');
   static const WorkspaceEvent DeleteView = WorkspaceEvent._(204, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteView');
   static const WorkspaceEvent OpenView = WorkspaceEvent._(205, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OpenView');
-  static const WorkspaceEvent UpdateViewData = WorkspaceEvent._(206, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateViewData');
+  static const WorkspaceEvent SaveViewData = WorkspaceEvent._(206, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SaveViewData');
+  static const WorkspaceEvent ApplyChangeset = WorkspaceEvent._(207, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ApplyChangeset');
 
   static const $core.List<WorkspaceEvent> values = <WorkspaceEvent> [
     CreateWorkspace,
@@ -43,7 +44,8 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
     UpdateView,
     DeleteView,
     OpenView,
-    UpdateViewData,
+    SaveViewData,
+    ApplyChangeset,
   ];
 
   static final $core.Map<$core.int, WorkspaceEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 3 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart

@@ -27,9 +27,10 @@ const WorkspaceEvent$json = const {
     const {'1': 'UpdateView', '2': 203},
     const {'1': 'DeleteView', '2': 204},
     const {'1': 'OpenView', '2': 205},
-    const {'1': 'UpdateViewData', '2': 206},
+    const {'1': 'SaveViewData', '2': 206},
+    const {'1': 'ApplyChangeset', '2': 207},
   ],
 };
 
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESDQoIT3BlblZpZXcQzQESEwoOVXBkYXRlVmlld0RhdGEQzgE=');
+final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESDQoIT3BlblZpZXcQzQESEQoMU2F2ZVZpZXdEYXRhEM4BEhMKDkFwcGx5Q2hhbmdlc2V0EM8B');

+ 4 - 4
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pb.dart

@@ -137,7 +137,7 @@ class CreateViewParams extends $pb.GeneratedMessage {
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
     ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
     ..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
-    ..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
+    ..a<$core.List<$core.int>>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
     ..hasRequiredFields = false
   ;
 
@@ -148,7 +148,7 @@ class CreateViewParams extends $pb.GeneratedMessage {
     $core.String? desc,
     $core.String? thumbnail,
     ViewType? viewType,
-    $core.String? data,
+    $core.List<$core.int>? data,
   }) {
     final _result = create();
     if (belongToId != null) {
@@ -238,9 +238,9 @@ class CreateViewParams extends $pb.GeneratedMessage {
   void clearViewType() => clearField(5);
 
   @$pb.TagNumber(6)
-  $core.String get data => $_getSZ(5);
+  $core.List<$core.int> get data => $_getN(5);
   @$pb.TagNumber(6)
-  set data($core.String v) { $_setString(5, v); }
+  set data($core.List<$core.int> v) { $_setBytes(5, v); }
   @$pb.TagNumber(6)
   $core.bool hasData() => $_has(5);
   @$pb.TagNumber(6)

+ 2 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pbjson.dart

@@ -45,12 +45,12 @@ const CreateViewParams$json = const {
     const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
     const {'1': 'thumbnail', '3': 4, '4': 1, '5': 9, '10': 'thumbnail'},
     const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
-    const {'1': 'data', '3': 6, '4': 1, '5': 9, '10': 'data'},
+    const {'1': 'data', '3': 6, '4': 1, '5': 12, '10': 'data'},
   ],
 };
 
 /// Descriptor for `CreateViewParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlEhIKBGRhdGEYBiABKAlSBGRhdGE=');
+final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlEhIKBGRhdGEYBiABKAxSBGRhdGE=');
 @$core.Deprecated('Use viewDescriptor instead')
 const View$json = const {
   '1': 'View',

+ 78 - 17
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_update.pb.dart

@@ -319,17 +319,17 @@ class UpdateViewParams extends $pb.GeneratedMessage {
   void clearIsTrash() => clearField(5);
 }
 
-class UpdateViewDataRequest extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UpdateViewDataRequest', createEmptyInstance: create)
+class SaveViewDataRequest extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SaveViewDataRequest', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
+    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
     ..hasRequiredFields = false
   ;
 
-  UpdateViewDataRequest._() : super();
-  factory UpdateViewDataRequest({
+  SaveViewDataRequest._() : super();
+  factory SaveViewDataRequest({
     $core.String? viewId,
-    $core.String? data,
+    $core.List<$core.int>? data,
   }) {
     final _result = create();
     if (viewId != null) {
@@ -340,26 +340,26 @@ class UpdateViewDataRequest extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory UpdateViewDataRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory UpdateViewDataRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory SaveViewDataRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SaveViewDataRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  UpdateViewDataRequest clone() => UpdateViewDataRequest()..mergeFromMessage(this);
+  SaveViewDataRequest clone() => SaveViewDataRequest()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  UpdateViewDataRequest copyWith(void Function(UpdateViewDataRequest) updates) => super.copyWith((message) => updates(message as UpdateViewDataRequest)) as UpdateViewDataRequest; // ignore: deprecated_member_use
+  SaveViewDataRequest copyWith(void Function(SaveViewDataRequest) updates) => super.copyWith((message) => updates(message as SaveViewDataRequest)) as SaveViewDataRequest; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static UpdateViewDataRequest create() => UpdateViewDataRequest._();
-  UpdateViewDataRequest createEmptyInstance() => create();
-  static $pb.PbList<UpdateViewDataRequest> createRepeated() => $pb.PbList<UpdateViewDataRequest>();
+  static SaveViewDataRequest create() => SaveViewDataRequest._();
+  SaveViewDataRequest createEmptyInstance() => create();
+  static $pb.PbList<SaveViewDataRequest> createRepeated() => $pb.PbList<SaveViewDataRequest>();
   @$core.pragma('dart2js:noInline')
-  static UpdateViewDataRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UpdateViewDataRequest>(create);
-  static UpdateViewDataRequest? _defaultInstance;
+  static SaveViewDataRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SaveViewDataRequest>(create);
+  static SaveViewDataRequest? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get viewId => $_getSZ(0);
@@ -371,9 +371,70 @@ class UpdateViewDataRequest extends $pb.GeneratedMessage {
   void clearViewId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.String get data => $_getSZ(1);
+  $core.List<$core.int> get data => $_getN(1);
   @$pb.TagNumber(2)
-  set data($core.String v) { $_setString(1, v); }
+  set data($core.List<$core.int> v) { $_setBytes(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasData() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearData() => clearField(2);
+}
+
+class ApplyChangesetRequest extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ApplyChangesetRequest', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId')
+    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false
+  ;
+
+  ApplyChangesetRequest._() : super();
+  factory ApplyChangesetRequest({
+    $core.String? viewId,
+    $core.List<$core.int>? data,
+  }) {
+    final _result = create();
+    if (viewId != null) {
+      _result.viewId = viewId;
+    }
+    if (data != null) {
+      _result.data = data;
+    }
+    return _result;
+  }
+  factory ApplyChangesetRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory ApplyChangesetRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  ApplyChangesetRequest clone() => ApplyChangesetRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  ApplyChangesetRequest copyWith(void Function(ApplyChangesetRequest) updates) => super.copyWith((message) => updates(message as ApplyChangesetRequest)) as ApplyChangesetRequest; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static ApplyChangesetRequest create() => ApplyChangesetRequest._();
+  ApplyChangesetRequest createEmptyInstance() => create();
+  static $pb.PbList<ApplyChangesetRequest> createRepeated() => $pb.PbList<ApplyChangesetRequest>();
+  @$core.pragma('dart2js:noInline')
+  static ApplyChangesetRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplyChangesetRequest>(create);
+  static ApplyChangesetRequest? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get viewId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set viewId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasViewId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearViewId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.List<$core.int> get data => $_getN(1);
+  @$pb.TagNumber(2)
+  set data($core.List<$core.int> v) { $_setBytes(1, v); }
   @$pb.TagNumber(2)
   $core.bool hasData() => $_has(1);
   @$pb.TagNumber(2)

+ 17 - 6
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_update.pbjson.dart

@@ -48,14 +48,25 @@ const UpdateViewParams$json = const {
 
 /// Descriptor for `UpdateViewParams`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List updateViewParamsDescriptor = $convert.base64Decode('ChBVcGRhdGVWaWV3UGFyYW1zEhcKB3ZpZXdfaWQYASABKAlSBnZpZXdJZBIUCgRuYW1lGAIgASgJSABSBG5hbWUSFAoEZGVzYxgDIAEoCUgBUgRkZXNjEh4KCXRodW1ibmFpbBgEIAEoCUgCUgl0aHVtYm5haWwSGwoIaXNfdHJhc2gYBSABKAhIA1IHaXNUcmFzaEINCgtvbmVfb2ZfbmFtZUINCgtvbmVfb2ZfZGVzY0ISChBvbmVfb2ZfdGh1bWJuYWlsQhEKD29uZV9vZl9pc190cmFzaA==');
-@$core.Deprecated('Use updateViewDataRequestDescriptor instead')
-const UpdateViewDataRequest$json = const {
-  '1': 'UpdateViewDataRequest',
+@$core.Deprecated('Use saveViewDataRequestDescriptor instead')
+const SaveViewDataRequest$json = const {
+  '1': 'SaveViewDataRequest',
   '2': const [
     const {'1': 'view_id', '3': 1, '4': 1, '5': 9, '10': 'viewId'},
-    const {'1': 'data', '3': 2, '4': 1, '5': 9, '10': 'data'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'},
   ],
 };
 
-/// Descriptor for `UpdateViewDataRequest`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List updateViewDataRequestDescriptor = $convert.base64Decode('ChVVcGRhdGVWaWV3RGF0YVJlcXVlc3QSFwoHdmlld19pZBgBIAEoCVIGdmlld0lkEhIKBGRhdGEYAiABKAlSBGRhdGE=');
+/// Descriptor for `SaveViewDataRequest`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List saveViewDataRequestDescriptor = $convert.base64Decode('ChNTYXZlVmlld0RhdGFSZXF1ZXN0EhcKB3ZpZXdfaWQYASABKAlSBnZpZXdJZBISCgRkYXRhGAIgASgMUgRkYXRh');
+@$core.Deprecated('Use applyChangesetRequestDescriptor instead')
+const ApplyChangesetRequest$json = const {
+  '1': 'ApplyChangesetRequest',
+  '2': const [
+    const {'1': 'view_id', '3': 1, '4': 1, '5': 9, '10': 'viewId'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'},
+  ],
+};
+
+/// Descriptor for `ApplyChangesetRequest`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List applyChangesetRequestDescriptor = $convert.base64Decode('ChVBcHBseUNoYW5nZXNldFJlcXVlc3QSFwoHdmlld19pZBgBIAEoCVIGdmlld0lkEhIKBGRhdGEYAiABKAxSBGRhdGE=');

+ 1 - 1
backend/migrations/20210909115140_doc.sql

@@ -2,5 +2,5 @@
 CREATE TABLE IF NOT EXISTS doc_table(
     id uuid NOT NULL,
     PRIMARY KEY (id),
-    data TEXT NOT NULL
+    data bytea NOT NULL DEFAULT ''
 );

+ 1 - 1
backend/src/entities/doc.rs

@@ -5,7 +5,7 @@ pub(crate) const DOC_TABLE: &'static str = "doc_table";
 #[derive(Debug, Clone, sqlx::FromRow)]
 pub struct DocTable {
     pub(crate) id: uuid::Uuid,
-    pub(crate) data: String,
+    pub(crate) data: Vec<u8>,
 }
 
 impl std::convert::Into<Doc> for DocTable {

+ 3 - 6
backend/src/service/doc_service/doc.rs

@@ -4,7 +4,7 @@ use crate::{
     sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
 };
 use anyhow::Context;
-use flowy_document::protobuf::{CreateDocParams, Doc, QueryDocParams, UpdateDocParams};
+use flowy_document::protobuf::{CreateDocParams, Doc, QueryDocParams, SaveDocParams};
 use flowy_net::{errors::ServerError, response::FlowyResponse};
 use sqlx::{postgres::PgArguments, PgPool, Postgres};
 use uuid::Uuid;
@@ -55,7 +55,7 @@ pub(crate) async fn read_doc(
 
 pub(crate) async fn update_doc(
     pool: &PgPool,
-    mut params: UpdateDocParams,
+    mut params: SaveDocParams,
 ) -> Result<FlowyResponse, ServerError> {
     let doc_id = Uuid::parse_str(&params.id)?;
     let mut transaction = pool
@@ -63,10 +63,7 @@ pub(crate) async fn update_doc(
         .await
         .context("Failed to acquire a Postgres connection to update doc")?;
 
-    let data = match params.has_data() {
-        true => Some(params.take_data()),
-        false => None,
-    };
+    let data = Some(params.take_data());
 
     let (sql, args) = SqlBuilder::update(DOC_TABLE)
         .add_some_arg("data", data)

+ 2 - 2
backend/src/service/doc_service/router.rs

@@ -4,7 +4,7 @@ use actix_web::{
 };
 use sqlx::PgPool;
 
-use flowy_document::protobuf::{QueryDocParams, UpdateDocParams};
+use flowy_document::protobuf::{QueryDocParams, SaveDocParams};
 use flowy_net::errors::ServerError;
 
 use crate::service::{
@@ -25,7 +25,7 @@ pub async fn update_handler(
     payload: Payload,
     pool: Data<PgPool>,
 ) -> Result<HttpResponse, ServerError> {
-    let params: UpdateDocParams = parse_from_payload(payload).await?;
+    let params: SaveDocParams = parse_from_payload(payload).await?;
     let response = update_doc(pool.get_ref(), params).await?;
     Ok(response.into())
 }

+ 2 - 5
backend/src/service/doc_service/sql_builder.rs

@@ -14,14 +14,11 @@ pub struct NewDocSqlBuilder {
 
 impl NewDocSqlBuilder {
     pub fn new(id: Uuid) -> Self {
-        let table = DocTable {
-            id,
-            data: "".to_owned(),
-        };
+        let table = DocTable { id, data: vec![] };
         Self { table }
     }
 
-    pub fn data(mut self, data: String) -> Self {
+    pub fn data(mut self, data: Vec<u8>) -> Self {
         self.table.data = data;
         self
     }

+ 6 - 1
backend/src/service/workspace_service/user_default/user_default.rs

@@ -9,6 +9,7 @@ use crate::{
     },
     sqlx_ext::{map_sqlx_error, DBTransaction},
 };
+use flowy_workspace::entities::view::default_delta;
 
 pub async fn create_default_workspace(
     transaction: &mut DBTransaction<'_>,
@@ -63,11 +64,15 @@ async fn create_view(transaction: &mut DBTransaction<'_>, app: &App) -> Result<V
         desc: "View created by AppFlowy".to_string(),
         thumbnail: "123.png".to_string(),
         view_type: ViewType::Doc,
-        data: "[{\"insert\":\"\\n\"}]".to_string(),
+        data: default_delta(),
         unknown_fields: Default::default(),
         cached_size: Default::default(),
     };
 
+    let name = "DefaultView".to_string();
+    let desc = "View created by AppFlowy Server".to_string();
+    let thumbnail = "http://1.png".to_string();
+
     let view = create_view_with_transaction(transaction, params).await?;
 
     Ok(view)

+ 6 - 9
backend/tests/api/helper.rs

@@ -4,7 +4,7 @@ use backend::{
 };
 
 use flowy_document::{
-    entities::doc::{Doc, QueryDocParams, UpdateDocParams},
+    entities::doc::{Doc, QueryDocParams, SaveDocParams},
     prelude::*,
 };
 use flowy_user::{errors::UserError, prelude::*};
@@ -278,14 +278,11 @@ pub(crate) async fn create_test_app(server: &TestServer, workspace_id: &str) ->
 }
 
 pub(crate) async fn create_test_view(application: &TestServer, app_id: &str) -> View {
-    let params = CreateViewParams {
-        belong_to_id: app_id.to_string(),
-        name: "My first view".to_string(),
-        desc: "This is my first view".to_string(),
-        thumbnail: "http://1.png".to_string(),
-        view_type: ViewType::Doc,
-        data: "".to_owned(),
-    };
+    let name = "My first view".to_string();
+    let desc = "This is my first view".to_string();
+    let thumbnail = "http://1.png".to_string();
+
+    let params = CreateViewParams::new(app_id.to_owned(), name, desc, ViewType::Doc, thumbnail);
     let app = application.create_view(params).await;
     app
 }

+ 2 - 0
rust-lib/dart-ffi/src/protobuf/mod.rs

@@ -1,2 +1,4 @@
+
 mod model;
 pub use model::*;
+        

+ 5 - 5
rust-lib/dart-ffi/src/protobuf/model/mod.rs

@@ -1,7 +1,7 @@
-// Auto-generated, do not edit
+// Auto-generated, do not edit 
 
-mod ffi_response;
-pub use ffi_response::*;
+mod ffi_response; 
+pub use ffi_response::*; 
 
-mod ffi_request;
-pub use ffi_request::*;
+mod ffi_request; 
+pub use ffi_request::*; 

+ 1 - 1
rust-lib/flowy-database/migrations/2021-07-22-234458_flowy-editor/up.sql

@@ -1,6 +1,6 @@
 -- Your SQL goes here
 CREATE TABLE doc_table (
     id TEXT NOT NULL PRIMARY KEY,
-    data TEXT NOT NULL DEFAULT '',
+    data BLOB NOT NULL DEFAULT (x''),
     version BIGINT NOT NULL DEFAULT 0
 );

+ 8 - 2
rust-lib/flowy-database/src/schema.rs

@@ -16,7 +16,7 @@ table! {
 table! {
     doc_table (id) {
         id -> Text,
-        data -> Text,
+        data -> Binary,
         version -> BigInt,
     }
 }
@@ -58,4 +58,10 @@ table! {
     }
 }
 
-allow_tables_to_appear_in_same_query!(app_table, doc_table, user_table, view_table, workspace_table,);
+allow_tables_to_appear_in_same_query!(
+    app_table,
+    doc_table,
+    user_table,
+    view_table,
+    workspace_table,
+);

+ 8 - 5
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -41,7 +41,8 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "CurrentWorkspace"
         | "UpdateViewRequest"
         | "UpdateViewParams"
-        | "UpdateViewDataRequest"
+        | "SaveViewDataRequest"
+        | "ApplyChangesetRequest"
         | "DeleteViewRequest"
         | "DeleteViewParams"
         | "QueryViewRequest"
@@ -54,7 +55,8 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "WorkspaceError"
         | "CreateDocParams"
         | "Doc"
-        | "UpdateDocParams"
+        | "SaveDocParams"
+        | "ApplyChangesetParams"
         | "QueryDocParams"
         | "DocError"
         | "FFIRequest"
@@ -69,17 +71,18 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "UserProfile"
         | "UpdateUserRequest"
         | "UpdateUserParams"
-        | "UserError" => TypeCategory::Protobuf,
+        | "UserError"
+        => TypeCategory::Protobuf,
         "ViewType"
         | "WorkspaceEvent"
         | "ErrorCode"
         | "WorkspaceObservable"
-        | "EditorEvent"
         | "DocObservable"
         | "FFIStatusCode"
         | "UserStatus"
         | "UserEvent"
-        | "UserObservable" => TypeCategory::Enum,
+        | "UserObservable"
+        => TypeCategory::Enum,
 
         "Option" => TypeCategory::Opt,
         _ => TypeCategory::Primitive,

+ 15 - 11
rust-lib/flowy-document/src/entities/doc/doc.rs

@@ -7,16 +7,11 @@ pub struct CreateDocParams {
     pub id: String,
 
     #[pb(index = 2)]
-    pub data: String,
+    pub data: Vec<u8>,
 }
 
 impl CreateDocParams {
-    pub fn new(id: &str, data: &str) -> Self {
-        Self {
-            id: id.to_owned(),
-            data: data.to_owned(),
-        }
-    }
+    pub fn new(id: &str, data: Vec<u8>) -> Self { Self { id: id.to_owned(), data } }
 }
 
 #[derive(ProtoBuf, Default, Debug, Clone, Eq, PartialEq)]
@@ -25,16 +20,25 @@ pub struct Doc {
     pub id: String,
 
     #[pb(index = 2)]
-    pub data: String,
+    pub data: Vec<u8>,
 }
 
 #[derive(ProtoBuf, Default, Debug, Clone)]
-pub struct UpdateDocParams {
+pub struct SaveDocParams {
     #[pb(index = 1)]
     pub id: String,
 
-    #[pb(index = 2, one_of)]
-    pub data: Option<String>,
+    #[pb(index = 2)]
+    pub data: Vec<u8>,
+}
+
+#[derive(ProtoBuf, Default, Debug, Clone)]
+pub struct ApplyChangesetParams {
+    #[pb(index = 1)]
+    pub id: String,
+
+    #[pb(index = 2)]
+    pub data: Vec<u8>,
 }
 
 #[derive(ProtoBuf, Default, Debug, Clone)]

+ 4 - 0
rust-lib/flowy-document/src/errors.rs

@@ -48,6 +48,10 @@ impl std::convert::From<flowy_database::Error> for DocError {
     }
 }
 
+impl std::convert::From<flowy_ot::errors::OTError> for DocError {
+    fn from(error: flowy_ot::errors::OTError) -> Self { ErrorBuilder::new(ErrorCode::InternalError).error(error).build() }
+}
+
 // impl std::convert::From<::r2d2::Error> for DocError {
 //     fn from(error: r2d2::Error) -> Self {
 // ErrorBuilder::new(ErrorCode::InternalError).error(error).build() } }

+ 24 - 11
rust-lib/flowy-document/src/module.rs

@@ -1,9 +1,12 @@
 use crate::{
     errors::DocError,
-    services::{doc_controller::DocController, doc_manager::DocManager, server::construct_doc_server},
+    services::{doc_cache::DocCache, server::construct_doc_server},
 };
 
-use crate::entities::doc::{CreateDocParams, Doc, QueryDocParams, UpdateDocParams};
+use crate::{
+    entities::doc::{ApplyChangesetParams, CreateDocParams, Doc, QueryDocParams, SaveDocParams},
+    services::doc_controller::DocController,
+};
 use diesel::SqliteConnection;
 use flowy_database::ConnectionPool;
 use std::sync::Arc;
@@ -15,21 +18,17 @@ pub trait DocumentUser: Send + Sync {
     fn token(&self) -> Result<String, DocError>;
 }
 
-pub enum DocumentType {
-    Doc,
-}
-
 pub struct FlowyDocument {
     controller: Arc<DocController>,
-    manager: Arc<DocManager>,
+    cache: Arc<DocCache>,
 }
 
 impl FlowyDocument {
     pub fn new(user: Arc<dyn DocumentUser>) -> FlowyDocument {
         let server = construct_doc_server();
-        let manager = Arc::new(DocManager::new());
+        let cache = Arc::new(DocCache::new());
         let controller = Arc::new(DocController::new(server.clone(), user.clone()));
-        Self { controller, manager }
+        Self { controller, cache }
     }
 
     pub fn create(&self, params: CreateDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
@@ -38,18 +37,32 @@ impl FlowyDocument {
     }
 
     pub fn delete(&self, params: QueryDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
+        let _ = self.cache.close(&params.doc_id)?;
         let _ = self.controller.delete(params.into(), conn)?;
         Ok(())
     }
 
     pub async fn open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Doc, DocError> {
         let doc = self.controller.open(params, pool).await?;
+        let _ = self.cache.open(&doc.id, doc.data.clone())?;
 
         Ok(doc)
     }
 
-    pub fn update(&self, params: UpdateDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
-        let _ = self.controller.update(params, conn)?;
+    pub async fn update(&self, params: SaveDocParams, pool: Arc<ConnectionPool>) -> Result<(), DocError> {
+        let _ = self.controller.update(params, &*pool.get().unwrap())?;
+        Ok(())
+    }
+
+    pub async fn apply_changeset(&self, params: ApplyChangesetParams) -> Result<(), DocError> {
+        let _ = self
+            .cache
+            .mut_doc(&params.id, |doc| {
+                log::debug!("Document:{} applying delta", &params.id);
+                let _ = doc.apply_changeset(params.data.clone())?;
+                Ok(())
+            })
+            .await?;
         Ok(())
     }
 }

+ 2 - 0
rust-lib/flowy-document/src/protobuf/mod.rs

@@ -1,2 +1,4 @@
+
 mod model;
 pub use model::*;
+        

+ 297 - 129
rust-lib/flowy-document/src/protobuf/model/doc.rs

@@ -27,7 +27,7 @@
 pub struct CreateDocParams {
     // message fields
     pub id: ::std::string::String,
-    pub data: ::std::string::String,
+    pub data: ::std::vec::Vec<u8>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -70,10 +70,10 @@ impl CreateDocParams {
         ::std::mem::replace(&mut self.id, ::std::string::String::new())
     }
 
-    // string data = 2;
+    // bytes data = 2;
 
 
-    pub fn get_data(&self) -> &str {
+    pub fn get_data(&self) -> &[u8] {
         &self.data
     }
     pub fn clear_data(&mut self) {
@@ -81,19 +81,19 @@ impl CreateDocParams {
     }
 
     // Param is passed by value, moved
-    pub fn set_data(&mut self, v: ::std::string::String) {
+    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
         self.data = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_data(&mut self) -> &mut ::std::string::String {
+    pub fn mut_data(&mut self) -> &mut ::std::vec::Vec<u8> {
         &mut self.data
     }
 
     // Take field
-    pub fn take_data(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.data, ::std::string::String::new())
+    pub fn take_data(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.data, ::std::vec::Vec::new())
     }
 }
 
@@ -110,7 +110,7 @@ impl ::protobuf::Message for CreateDocParams {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -128,7 +128,7 @@ impl ::protobuf::Message for CreateDocParams {
             my_size += ::protobuf::rt::string_size(1, &self.id);
         }
         if !self.data.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.data);
+            my_size += ::protobuf::rt::bytes_size(2, &self.data);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -140,7 +140,7 @@ impl ::protobuf::Message for CreateDocParams {
             os.write_string(1, &self.id)?;
         }
         if !self.data.is_empty() {
-            os.write_string(2, &self.data)?;
+            os.write_bytes(2, &self.data)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -185,7 +185,7 @@ impl ::protobuf::Message for CreateDocParams {
                 |m: &CreateDocParams| { &m.id },
                 |m: &mut CreateDocParams| { &mut m.id },
             ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
                 "data",
                 |m: &CreateDocParams| { &m.data },
                 |m: &mut CreateDocParams| { &mut m.data },
@@ -228,7 +228,7 @@ impl ::protobuf::reflect::ProtobufValue for CreateDocParams {
 pub struct Doc {
     // message fields
     pub id: ::std::string::String,
-    pub data: ::std::string::String,
+    pub data: ::std::vec::Vec<u8>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -271,10 +271,10 @@ impl Doc {
         ::std::mem::replace(&mut self.id, ::std::string::String::new())
     }
 
-    // string data = 2;
+    // bytes data = 2;
 
 
-    pub fn get_data(&self) -> &str {
+    pub fn get_data(&self) -> &[u8] {
         &self.data
     }
     pub fn clear_data(&mut self) {
@@ -282,19 +282,19 @@ impl Doc {
     }
 
     // Param is passed by value, moved
-    pub fn set_data(&mut self, v: ::std::string::String) {
+    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
         self.data = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_data(&mut self) -> &mut ::std::string::String {
+    pub fn mut_data(&mut self) -> &mut ::std::vec::Vec<u8> {
         &mut self.data
     }
 
     // Take field
-    pub fn take_data(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.data, ::std::string::String::new())
+    pub fn take_data(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.data, ::std::vec::Vec::new())
     }
 }
 
@@ -311,7 +311,7 @@ impl ::protobuf::Message for Doc {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -329,7 +329,7 @@ impl ::protobuf::Message for Doc {
             my_size += ::protobuf::rt::string_size(1, &self.id);
         }
         if !self.data.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.data);
+            my_size += ::protobuf::rt::bytes_size(2, &self.data);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -341,7 +341,7 @@ impl ::protobuf::Message for Doc {
             os.write_string(1, &self.id)?;
         }
         if !self.data.is_empty() {
-            os.write_string(2, &self.data)?;
+            os.write_bytes(2, &self.data)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -386,7 +386,7 @@ impl ::protobuf::Message for Doc {
                 |m: &Doc| { &m.id },
                 |m: &mut Doc| { &mut m.id },
             ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
                 "data",
                 |m: &Doc| { &m.data },
                 |m: &mut Doc| { &mut m.data },
@@ -426,29 +426,23 @@ impl ::protobuf::reflect::ProtobufValue for Doc {
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct UpdateDocParams {
+pub struct SaveDocParams {
     // message fields
     pub id: ::std::string::String,
-    // message oneof groups
-    pub one_of_data: ::std::option::Option<UpdateDocParams_oneof_one_of_data>,
+    pub data: ::std::vec::Vec<u8>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a UpdateDocParams {
-    fn default() -> &'a UpdateDocParams {
-        <UpdateDocParams as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a SaveDocParams {
+    fn default() -> &'a SaveDocParams {
+        <SaveDocParams as ::protobuf::Message>::default_instance()
     }
 }
 
-#[derive(Clone,PartialEq,Debug)]
-pub enum UpdateDocParams_oneof_one_of_data {
-    data(::std::string::String),
-}
-
-impl UpdateDocParams {
-    pub fn new() -> UpdateDocParams {
+impl SaveDocParams {
+    pub fn new() -> SaveDocParams {
         ::std::default::Default::default()
     }
 
@@ -478,57 +472,235 @@ impl UpdateDocParams {
         ::std::mem::replace(&mut self.id, ::std::string::String::new())
     }
 
-    // string data = 2;
+    // bytes data = 2;
 
 
-    pub fn get_data(&self) -> &str {
-        match self.one_of_data {
-            ::std::option::Option::Some(UpdateDocParams_oneof_one_of_data::data(ref v)) => v,
-            _ => "",
-        }
+    pub fn get_data(&self) -> &[u8] {
+        &self.data
     }
     pub fn clear_data(&mut self) {
-        self.one_of_data = ::std::option::Option::None;
+        self.data.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
+        self.data = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_data(&mut self) -> &mut ::std::vec::Vec<u8> {
+        &mut self.data
+    }
+
+    // Take field
+    pub fn take_data(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.data, ::std::vec::Vec::new())
+    }
+}
+
+impl ::protobuf::Message for SaveDocParams {
+    fn is_initialized(&self) -> bool {
+        true
     }
 
-    pub fn has_data(&self) -> bool {
-        match self.one_of_data {
-            ::std::option::Option::Some(UpdateDocParams_oneof_one_of_data::data(..)) => true,
-            _ => false,
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
         }
+        ::std::result::Result::Ok(())
     }
 
-    // Param is passed by value, moved
-    pub fn set_data(&mut self, v: ::std::string::String) {
-        self.one_of_data = ::std::option::Option::Some(UpdateDocParams_oneof_one_of_data::data(v))
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
+        if !self.data.is_empty() {
+            my_size += ::protobuf::rt::bytes_size(2, &self.data);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
     }
 
-    // Mutable pointer to the field.
-    pub fn mut_data(&mut self) -> &mut ::std::string::String {
-        if let ::std::option::Option::Some(UpdateDocParams_oneof_one_of_data::data(_)) = self.one_of_data {
-        } else {
-            self.one_of_data = ::std::option::Option::Some(UpdateDocParams_oneof_one_of_data::data(::std::string::String::new()));
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.id.is_empty() {
+            os.write_string(1, &self.id)?;
         }
-        match self.one_of_data {
-            ::std::option::Option::Some(UpdateDocParams_oneof_one_of_data::data(ref mut v)) => v,
-            _ => panic!(),
+        if !self.data.is_empty() {
+            os.write_bytes(2, &self.data)?;
         }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> SaveDocParams {
+        SaveDocParams::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "id",
+                |m: &SaveDocParams| { &m.id },
+                |m: &mut SaveDocParams| { &mut m.id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
+                "data",
+                |m: &SaveDocParams| { &m.data },
+                |m: &mut SaveDocParams| { &mut m.data },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SaveDocParams>(
+                "SaveDocParams",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static SaveDocParams {
+        static instance: ::protobuf::rt::LazyV2<SaveDocParams> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SaveDocParams::new)
+    }
+}
+
+impl ::protobuf::Clear for SaveDocParams {
+    fn clear(&mut self) {
+        self.id.clear();
+        self.data.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for SaveDocParams {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for SaveDocParams {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct ApplyChangesetParams {
+    // message fields
+    pub id: ::std::string::String,
+    pub data: ::std::vec::Vec<u8>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a ApplyChangesetParams {
+    fn default() -> &'a ApplyChangesetParams {
+        <ApplyChangesetParams as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl ApplyChangesetParams {
+    pub fn new() -> ApplyChangesetParams {
+        ::std::default::Default::default()
+    }
+
+    // string id = 1;
+
+
+    pub fn get_id(&self) -> &str {
+        &self.id
+    }
+    pub fn clear_id(&mut self) {
+        self.id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
     }
 
     // Take field
-    pub fn take_data(&mut self) -> ::std::string::String {
-        if self.has_data() {
-            match self.one_of_data.take() {
-                ::std::option::Option::Some(UpdateDocParams_oneof_one_of_data::data(v)) => v,
-                _ => panic!(),
-            }
-        } else {
-            ::std::string::String::new()
-        }
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
+    }
+
+    // bytes data = 2;
+
+
+    pub fn get_data(&self) -> &[u8] {
+        &self.data
+    }
+    pub fn clear_data(&mut self) {
+        self.data.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
+        self.data = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_data(&mut self) -> &mut ::std::vec::Vec<u8> {
+        &mut self.data
+    }
+
+    // Take field
+    pub fn take_data(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.data, ::std::vec::Vec::new())
     }
 }
 
-impl ::protobuf::Message for UpdateDocParams {
+impl ::protobuf::Message for ApplyChangesetParams {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -541,10 +713,7 @@ impl ::protobuf::Message for UpdateDocParams {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
                 },
                 2 => {
-                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
-                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
-                    }
-                    self.one_of_data = ::std::option::Option::Some(UpdateDocParams_oneof_one_of_data::data(is.read_string()?));
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -561,12 +730,8 @@ impl ::protobuf::Message for UpdateDocParams {
         if !self.id.is_empty() {
             my_size += ::protobuf::rt::string_size(1, &self.id);
         }
-        if let ::std::option::Option::Some(ref v) = self.one_of_data {
-            match v {
-                &UpdateDocParams_oneof_one_of_data::data(ref v) => {
-                    my_size += ::protobuf::rt::string_size(2, &v);
-                },
-            };
+        if !self.data.is_empty() {
+            my_size += ::protobuf::rt::bytes_size(2, &self.data);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -577,12 +742,8 @@ impl ::protobuf::Message for UpdateDocParams {
         if !self.id.is_empty() {
             os.write_string(1, &self.id)?;
         }
-        if let ::std::option::Option::Some(ref v) = self.one_of_data {
-            match v {
-                &UpdateDocParams_oneof_one_of_data::data(ref v) => {
-                    os.write_string(2, v)?;
-                },
-            };
+        if !self.data.is_empty() {
+            os.write_bytes(2, &self.data)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -614,8 +775,8 @@ impl ::protobuf::Message for UpdateDocParams {
         Self::descriptor_static()
     }
 
-    fn new() -> UpdateDocParams {
-        UpdateDocParams::new()
+    fn new() -> ApplyChangesetParams {
+        ApplyChangesetParams::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -624,43 +785,43 @@ impl ::protobuf::Message for UpdateDocParams {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "id",
-                |m: &UpdateDocParams| { &m.id },
-                |m: &mut UpdateDocParams| { &mut m.id },
+                |m: &ApplyChangesetParams| { &m.id },
+                |m: &mut ApplyChangesetParams| { &mut m.id },
             ));
-            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
                 "data",
-                UpdateDocParams::has_data,
-                UpdateDocParams::get_data,
+                |m: &ApplyChangesetParams| { &m.data },
+                |m: &mut ApplyChangesetParams| { &mut m.data },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UpdateDocParams>(
-                "UpdateDocParams",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<ApplyChangesetParams>(
+                "ApplyChangesetParams",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static UpdateDocParams {
-        static instance: ::protobuf::rt::LazyV2<UpdateDocParams> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(UpdateDocParams::new)
+    fn default_instance() -> &'static ApplyChangesetParams {
+        static instance: ::protobuf::rt::LazyV2<ApplyChangesetParams> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(ApplyChangesetParams::new)
     }
 }
 
-impl ::protobuf::Clear for UpdateDocParams {
+impl ::protobuf::Clear for ApplyChangesetParams {
     fn clear(&mut self) {
         self.id.clear();
-        self.one_of_data = ::std::option::Option::None;
+        self.data.clear();
         self.unknown_fields.clear();
     }
 }
 
-impl ::std::fmt::Debug for UpdateDocParams {
+impl ::std::fmt::Debug for ApplyChangesetParams {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for UpdateDocParams {
+impl ::protobuf::reflect::ProtobufValue for ApplyChangesetParams {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -827,37 +988,44 @@ impl ::protobuf::reflect::ProtobufValue for QueryDocParams {
 
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\tdoc.proto\"5\n\x0fCreateDocParams\x12\x0e\n\x02id\x18\x01\x20\x01(\t\
-    R\x02id\x12\x12\n\x04data\x18\x02\x20\x01(\tR\x04data\")\n\x03Doc\x12\
+    R\x02id\x12\x12\n\x04data\x18\x02\x20\x01(\x0cR\x04data\")\n\x03Doc\x12\
     \x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04data\x18\x02\x20\x01\
-    (\tR\x04data\"F\n\x0fUpdateDocParams\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
-    \x02id\x12\x14\n\x04data\x18\x02\x20\x01(\tH\0R\x04dataB\r\n\x0bone_of_d\
-    ata\"'\n\x0eQueryDocParams\x12\x15\n\x06doc_id\x18\x01\x20\x01(\tR\x05do\
-    cIdJ\x8e\x04\n\x06\x12\x04\0\0\x10\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\
-    \n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\
-    \x17\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\x05\x04\0\x02\0\
-    \x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\r\n\
-    \x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x10\x11\n\x0b\n\x04\x04\0\x02\x01\
-    \x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\
-    \x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\
-    \x01\x03\x12\x03\x04\x12\x13\n\n\n\x02\x04\x01\x12\x04\x06\0\t\x01\n\n\n\
-    \x03\x04\x01\x01\x12\x03\x06\x08\x0b\n\x0b\n\x04\x04\x01\x02\0\x12\x03\
-    \x07\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x07\x04\n\n\x0c\n\x05\
-    \x04\x01\x02\0\x01\x12\x03\x07\x0b\r\n\x0c\n\x05\x04\x01\x02\0\x03\x12\
-    \x03\x07\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\x08\x04\x14\n\x0c\n\
-    \x05\x04\x01\x02\x01\x05\x12\x03\x08\x04\n\n\x0c\n\x05\x04\x01\x02\x01\
-    \x01\x12\x03\x08\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x08\x12\
-    \x13\n\n\n\x02\x04\x02\x12\x04\n\0\r\x01\n\n\n\x03\x04\x02\x01\x12\x03\n\
-    \x08\x17\n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0b\x04\x12\n\x0c\n\x05\x04\
-    \x02\x02\0\x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\
-    \x0b\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0b\x10\x11\n\x0b\n\x04\
-    \x04\x02\x08\0\x12\x03\x0c\x04*\n\x0c\n\x05\x04\x02\x08\0\x01\x12\x03\
-    \x0c\n\x15\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\x0c\x18(\n\x0c\n\x05\x04\
-    \x02\x02\x01\x05\x12\x03\x0c\x18\x1e\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\
-    \x03\x0c\x1f#\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\x0c&'\n\n\n\x02\
-    \x04\x03\x12\x04\x0e\0\x10\x01\n\n\n\x03\x04\x03\x01\x12\x03\x0e\x08\x16\
-    \n\x0b\n\x04\x04\x03\x02\0\x12\x03\x0f\x04\x16\n\x0c\n\x05\x04\x03\x02\0\
-    \x05\x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x0f\x0b\x11\
-    \n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x0f\x14\x15b\x06proto3\
+    (\x0cR\x04data\"3\n\rSaveDocParams\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
+    \x02id\x12\x12\n\x04data\x18\x02\x20\x01(\x0cR\x04data\":\n\x14ApplyChan\
+    gesetParams\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04data\
+    \x18\x02\x20\x01(\x0cR\x04data\"'\n\x0eQueryDocParams\x12\x15\n\x06doc_i\
+    d\x18\x01\x20\x01(\tR\x05docIdJ\xf9\x04\n\x06\x12\x04\0\0\x14\x01\n\x08\
+    \n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\
+    \x03\x04\0\x01\x12\x03\x02\x08\x17\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\
+    \x04\x12\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\
+    \x02\0\x01\x12\x03\x03\x0b\r\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x10\
+    \x11\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\x02\
+    \x01\x05\x12\x03\x04\x04\t\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\n\
+    \x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x04\x01\
+    \x12\x04\x06\0\t\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\x08\x0b\n\x0b\n\
+    \x04\x04\x01\x02\0\x12\x03\x07\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\
+    \x03\x07\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07\x0b\r\n\x0c\n\
+    \x05\x04\x01\x02\0\x03\x12\x03\x07\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\
+    \x12\x03\x08\x04\x13\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x08\x04\t\n\
+    \x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x08\n\x0e\n\x0c\n\x05\x04\x01\x02\
+    \x01\x03\x12\x03\x08\x11\x12\n\n\n\x02\x04\x02\x12\x04\n\0\r\x01\n\n\n\
+    \x03\x04\x02\x01\x12\x03\n\x08\x15\n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0b\
+    \x04\x12\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\
+    \x02\x02\0\x01\x12\x03\x0b\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\
+    \x0b\x10\x11\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\x0c\x04\x13\n\x0c\n\x05\
+    \x04\x02\x02\x01\x05\x12\x03\x0c\x04\t\n\x0c\n\x05\x04\x02\x02\x01\x01\
+    \x12\x03\x0c\n\x0e\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\x0c\x11\x12\n\
+    \n\n\x02\x04\x03\x12\x04\x0e\0\x11\x01\n\n\n\x03\x04\x03\x01\x12\x03\x0e\
+    \x08\x1c\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x0f\x04\x12\n\x0c\n\x05\x04\
+    \x03\x02\0\x05\x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\
+    \x0f\x0b\r\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x0f\x10\x11\n\x0b\n\x04\
+    \x04\x03\x02\x01\x12\x03\x10\x04\x13\n\x0c\n\x05\x04\x03\x02\x01\x05\x12\
+    \x03\x10\x04\t\n\x0c\n\x05\x04\x03\x02\x01\x01\x12\x03\x10\n\x0e\n\x0c\n\
+    \x05\x04\x03\x02\x01\x03\x12\x03\x10\x11\x12\n\n\n\x02\x04\x04\x12\x04\
+    \x12\0\x14\x01\n\n\n\x03\x04\x04\x01\x12\x03\x12\x08\x16\n\x0b\n\x04\x04\
+    \x04\x02\0\x12\x03\x13\x04\x16\n\x0c\n\x05\x04\x04\x02\0\x05\x12\x03\x13\
+    \x04\n\n\x0c\n\x05\x04\x04\x02\0\x01\x12\x03\x13\x0b\x11\n\x0c\n\x05\x04\
+    \x04\x02\0\x03\x12\x03\x13\x14\x15b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 9 - 9
rust-lib/flowy-document/src/protobuf/model/mod.rs

@@ -1,13 +1,13 @@
-// Auto-generated, do not edit
+// Auto-generated, do not edit 
 
-mod observable;
-pub use observable::*;
+mod observable; 
+pub use observable::*; 
 
-mod errors;
-pub use errors::*;
+mod errors; 
+pub use errors::*; 
 
-mod event;
-pub use event::*;
+mod event; 
+pub use event::*; 
 
-mod doc;
-pub use doc::*;
+mod doc; 
+pub use doc::*; 

+ 8 - 4
rust-lib/flowy-document/src/protobuf/proto/doc.proto

@@ -2,15 +2,19 @@ syntax = "proto3";
 
 message CreateDocParams {
     string id = 1;
-    string data = 2;
+    bytes data = 2;
 }
 message Doc {
     string id = 1;
-    string data = 2;
+    bytes data = 2;
 }
-message UpdateDocParams {
+message SaveDocParams {
     string id = 1;
-    oneof one_of_data { string data = 2; };
+    bytes data = 2;
+}
+message ApplyChangesetParams {
+    string id = 1;
+    bytes data = 2;
 }
 message QueryDocParams {
     string doc_id = 1;

+ 10 - 5
rust-lib/flowy-document/src/services/doc_manager/doc_manager.rs → rust-lib/flowy-document/src/services/doc_cache.rs

@@ -3,6 +3,7 @@ use dashmap::{mapref::one::Ref, DashMap};
 use flowy_ot::{
     client::{Document, FlowyDoc},
     core::Delta,
+    errors::OTError,
 };
 use std::convert::TryInto;
 use tokio::sync::RwLock;
@@ -14,20 +15,24 @@ pub struct DocInfo {
     document: Document,
 }
 
-impl std::convert::From<String> for DocId {
-    fn from(s: String) -> Self { DocId(s) }
+impl<T> std::convert::From<T> for DocId
+where
+    T: ToString,
+{
+    fn from(s: T) -> Self { DocId(s.to_string()) }
 }
 
-pub(crate) struct DocManager {
+pub(crate) struct DocCache {
     inner: DashMap<DocId, RwLock<DocInfo>>,
 }
 
-impl DocManager {
+impl DocCache {
     pub(crate) fn new() -> Self { Self { inner: DashMap::new() } }
+
     pub(crate) fn open<T, D>(&self, id: T, data: D) -> Result<(), DocError>
     where
         T: Into<DocId>,
-        D: TryInto<Delta, Error = DocError>,
+        D: TryInto<Delta, Error = OTError>,
     {
         let doc_id = id.into();
         let delta = data.try_into()?;

+ 7 - 7
rust-lib/flowy-document/src/services/doc_controller.rs

@@ -1,5 +1,5 @@
 use crate::{
-    entities::doc::{CreateDocParams, Doc, QueryDocParams, UpdateDocParams},
+    entities::doc::{CreateDocParams, Doc, QueryDocParams, SaveDocParams},
     errors::{DocError, ErrorBuilder, ErrorCode},
     module::DocumentUser,
     services::server::Server,
@@ -10,7 +10,7 @@ use flowy_database::{ConnectionPool, SqliteConnection};
 use std::sync::Arc;
 use tokio::task::JoinHandle;
 
-pub struct DocController {
+pub(crate) struct DocController {
     server: Server,
     sql: Arc<DocTableSql>,
     user: Arc<dyn DocumentUser>,
@@ -23,7 +23,7 @@ impl DocController {
     }
 
     #[tracing::instrument(skip(self, conn), err)]
-    pub fn create(&self, params: CreateDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
+    pub(crate) fn create(&self, params: CreateDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
         let doc = Doc {
             id: params.id,
             data: params.data,
@@ -33,7 +33,7 @@ impl DocController {
     }
 
     #[tracing::instrument(level = "debug", skip(self, conn, params), err)]
-    pub fn update(&self, params: UpdateDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
+    pub(crate) fn update(&self, params: SaveDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
         let changeset = DocTableChangeset::new(params.clone());
         let _ = self.sql.update_doc_table(changeset, &*conn)?;
         let _ = self.update_doc_on_server(params)?;
@@ -42,7 +42,7 @@ impl DocController {
     }
 
     #[tracing::instrument(level = "debug", skip(self, pool), err)]
-    pub async fn open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Doc, DocError> {
+    pub(crate) async fn open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Doc, DocError> {
         match self._open(params.clone(), pool.clone()) {
             Ok(doc_table) => Ok(doc_table.into()),
             Err(error) => self.try_read_on_server(params, pool.clone(), error).await,
@@ -50,7 +50,7 @@ impl DocController {
     }
 
     #[tracing::instrument(level = "debug", skip(self, conn), err)]
-    pub fn delete(&self, params: QueryDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
+    pub(crate) fn delete(&self, params: QueryDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
         let _ = self.sql.delete_doc(&params.doc_id, &*conn)?;
         let _ = self.delete_doc_on_server(params)?;
         Ok(())
@@ -59,7 +59,7 @@ impl DocController {
 
 impl DocController {
     #[tracing::instrument(level = "debug", skip(self, params), err)]
-    fn update_doc_on_server(&self, params: UpdateDocParams) -> Result<(), DocError> {
+    fn update_doc_on_server(&self, params: SaveDocParams) -> Result<(), DocError> {
         let token = self.user.token()?;
         let server = self.server.clone();
         tokio::spawn(async move {

+ 0 - 3
rust-lib/flowy-document/src/services/doc_manager/mod.rs

@@ -1,3 +0,0 @@
-mod doc_manager;
-
-pub use doc_manager::*;

+ 2 - 2
rust-lib/flowy-document/src/services/mod.rs

@@ -1,3 +1,3 @@
-pub mod doc_controller;
-pub(crate) mod doc_manager;
+pub(crate) mod doc_cache;
+pub(crate) mod doc_controller;
 pub mod server;

+ 2 - 2
rust-lib/flowy-document/src/services/server/mod.rs

@@ -5,7 +5,7 @@ mod server_api_mock;
 pub use server_api::*;
 // TODO: ignore mock files in production
 use crate::{
-    entities::doc::{CreateDocParams, Doc, QueryDocParams, UpdateDocParams},
+    entities::doc::{CreateDocParams, Doc, QueryDocParams, SaveDocParams},
     errors::DocError,
 };
 use flowy_infra::future::ResultFuture;
@@ -18,7 +18,7 @@ pub trait DocumentServerAPI {
 
     fn read_doc(&self, token: &str, params: QueryDocParams) -> ResultFuture<Option<Doc>, DocError>;
 
-    fn update_doc(&self, token: &str, params: UpdateDocParams) -> ResultFuture<(), DocError>;
+    fn update_doc(&self, token: &str, params: SaveDocParams) -> ResultFuture<(), DocError>;
 
     fn delete_doc(&self, token: &str, params: QueryDocParams) -> ResultFuture<(), DocError>;
 }

+ 3 - 3
rust-lib/flowy-document/src/services/server/server_api.rs

@@ -1,5 +1,5 @@
 use crate::{
-    entities::doc::{CreateDocParams, Doc, QueryDocParams, UpdateDocParams},
+    entities::doc::{CreateDocParams, Doc, QueryDocParams, SaveDocParams},
     errors::DocError,
     services::server::DocumentServerAPI,
 };
@@ -19,7 +19,7 @@ impl DocumentServerAPI for DocServer {
         ResultFuture::new(async move { read_doc_request(&token, params, DOC_URL.as_ref()).await })
     }
 
-    fn update_doc(&self, token: &str, params: UpdateDocParams) -> ResultFuture<(), DocError> {
+    fn update_doc(&self, token: &str, params: SaveDocParams) -> ResultFuture<(), DocError> {
         let token = token.to_owned();
         ResultFuture::new(async move { update_doc_request(&token, params, DOC_URL.as_ref()).await })
     }
@@ -53,7 +53,7 @@ pub async fn read_doc_request(token: &str, params: QueryDocParams, url: &str) ->
     Ok(doc)
 }
 
-pub async fn update_doc_request(token: &str, params: UpdateDocParams, url: &str) -> Result<(), DocError> {
+pub async fn update_doc_request(token: &str, params: SaveDocParams, url: &str) -> Result<(), DocError> {
     let _ = request_builder()
         .patch(&url.to_owned())
         .header(HEADER_TOKEN, token)

+ 2 - 2
rust-lib/flowy-document/src/services/server/server_api_mock.rs

@@ -1,5 +1,5 @@
 use crate::{
-    entities::doc::{CreateDocParams, Doc, QueryDocParams, UpdateDocParams},
+    entities::doc::{CreateDocParams, Doc, QueryDocParams, SaveDocParams},
     errors::DocError,
     services::server::DocumentServerAPI,
 };
@@ -13,7 +13,7 @@ impl DocumentServerAPI for DocServerMock {
         ResultFuture::new(async { Ok(None) })
     }
 
-    fn update_doc(&self, _token: &str, _params: UpdateDocParams) -> ResultFuture<(), DocError> { ResultFuture::new(async { Ok(()) }) }
+    fn update_doc(&self, _token: &str, _params: SaveDocParams) -> ResultFuture<(), DocError> { ResultFuture::new(async { Ok(()) }) }
 
     fn delete_doc(&self, _token: &str, _params: QueryDocParams) -> ResultFuture<(), DocError> { ResultFuture::new(async { Ok(()) }) }
 }

+ 4 - 4
rust-lib/flowy-document/src/sql_tables/doc/doc_table.rs

@@ -1,11 +1,11 @@
-use crate::entities::doc::{Doc, UpdateDocParams};
+use crate::entities::doc::{Doc, SaveDocParams};
 use flowy_database::schema::doc_table;
 
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
 #[table_name = "doc_table"]
 pub(crate) struct DocTable {
     pub id: String,
-    pub data: String,
+    pub data: Vec<u8>,
     pub version: i64,
 }
 
@@ -23,11 +23,11 @@ impl DocTable {
 #[table_name = "doc_table"]
 pub(crate) struct DocTableChangeset {
     pub id: String,
-    pub data: Option<String>,
+    pub data: Vec<u8>,
 }
 
 impl DocTableChangeset {
-    pub(crate) fn new(params: UpdateDocParams) -> Self {
+    pub(crate) fn new(params: SaveDocParams) -> Self {
         Self {
             id: params.id,
             data: params.data,

+ 3 - 0
rust-lib/flowy-infra/src/lib.rs

@@ -9,5 +9,8 @@ pub mod future;
 pub mod kv;
 mod protobuf;
 
+#[allow(dead_code)]
 pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() }
+
+#[allow(dead_code)]
 pub fn timestamp() -> i64 { chrono::Utc::now().timestamp() }

+ 2 - 0
rust-lib/flowy-infra/src/protobuf/mod.rs

@@ -1,2 +1,4 @@
+
 mod model;
 pub use model::*;
+        

+ 3 - 3
rust-lib/flowy-infra/src/protobuf/model/mod.rs

@@ -1,4 +1,4 @@
-// Auto-generated, do not edit
+// Auto-generated, do not edit 
 
-mod kv;
-pub use kv::*;
+mod kv; 
+pub use kv::*; 

+ 1 - 1
rust-lib/flowy-log/src/lib.rs

@@ -41,7 +41,7 @@ impl Builder {
 
         let subscriber = tracing_subscriber::fmt()
             // .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
-            .with_target(true)
+            .with_target(false)
             .with_max_level(tracing::Level::TRACE)
             .with_writer(std::io::stderr)
             .with_thread_ids(false)

+ 2 - 0
rust-lib/flowy-observable/src/protobuf/mod.rs

@@ -1,2 +1,4 @@
+
 mod model;
 pub use model::*;
+        

+ 3 - 3
rust-lib/flowy-observable/src/protobuf/model/mod.rs

@@ -1,4 +1,4 @@
-// Auto-generated, do not edit
+// Auto-generated, do not edit 
 
-mod subject;
-pub use subject::*;
+mod subject; 
+pub use subject::*; 

+ 1 - 0
rust-lib/flowy-ot/Cargo.toml

@@ -16,6 +16,7 @@ chrono = "0.4.19"
 lazy_static = "1.4.0"
 url = "2.2"
 
+
 [dev-dependencies]
 criterion = "0.3"
 rand = "0.7.3"

+ 12 - 0
rust-lib/flowy-ot/src/client/document/document.rs

@@ -3,6 +3,7 @@ use crate::{
     core::*,
     errors::{ErrorBuilder, OTError, OTErrorCode, OTErrorCode::*},
 };
+use std::convert::TryInto;
 
 pub trait DocumentData {
     fn into_string(self) -> Result<String, OTError>;
@@ -50,6 +51,17 @@ impl Document {
 
     pub fn to_json(&self) -> String { self.delta.to_json() }
 
+    pub fn apply_changeset<T>(&mut self, changeset: T) -> Result<(), OTError>
+    where
+        T: TryInto<Delta, Error = OTError>,
+    {
+        let new_delta: Delta = changeset.try_into()?;
+        self.add_delta(&new_delta);
+
+        log::info!("Current delta: {:?}", self.to_json());
+        Ok(())
+    }
+
     pub fn insert<T: DocumentData>(&mut self, index: usize, data: T) -> Result<Delta, OTError> {
         let interval = Interval::new(index, index);
         let _ = validate_interval(&self.delta, &interval)?;

+ 24 - 3
rust-lib/flowy-ot/src/core/delta/delta.rs

@@ -7,9 +7,11 @@ use std::{
     cmp::{min, Ordering},
     fmt,
     iter::FromIterator,
+    str,
     str::FromStr,
 };
 
+// Opti: optimize the memory usage with Arc_mut or Cow
 #[derive(Clone, Debug, PartialEq)]
 pub struct Delta {
     pub ops: Vec<Operation>,
@@ -37,10 +39,15 @@ impl FromStr for Delta {
     }
 }
 
-impl<T: AsRef<str>> From<T> for Delta {
-    fn from(s: T) -> Delta { Delta::from_str(s.as_ref()).unwrap() }
+impl std::convert::TryFrom<Vec<u8>> for Delta {
+    type Error = OTError;
+    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { Delta::from_bytes(bytes) }
 }
 
+// impl<T: AsRef<Vec<u8>>> std::convert::From<T> for Delta {
+//     fn from(bytes: T) -> Self {
+// Delta::from_bytes(bytes.as_ref().to_vec()).unwrap() } }
+
 impl fmt::Display for Delta {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         // f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?;
@@ -69,10 +76,24 @@ impl Delta {
     pub fn to_json(&self) -> String { serde_json::to_string(self).unwrap_or("".to_owned()) }
 
     pub fn from_json(json: &str) -> Result<Self, OTError> {
-        let delta: Delta = serde_json::from_str(json)?;
+        let delta: Delta = serde_json::from_str(json).map_err(|e| {
+            log::error!("Deserialize  Delta failed: {:?}", e);
+            log::error!("{:?}", json);
+            e
+        })?;
         Ok(delta)
     }
 
+    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, OTError> {
+        let json = str::from_utf8(&bytes)?;
+        Self::from_json(json)
+    }
+
+    pub fn into_bytes(self) -> Vec<u8> {
+        let json = self.to_json();
+        json.into_bytes()
+    }
+
     #[inline]
     pub fn with_capacity(capacity: usize) -> Self {
         Self {

+ 5 - 1
rust-lib/flowy-ot/src/errors.rs

@@ -1,4 +1,4 @@
-use std::{error::Error, fmt};
+use std::{error::Error, fmt, str::Utf8Error};
 
 #[derive(Clone, Debug)]
 pub struct OTError {
@@ -22,6 +22,10 @@ impl std::convert::From<serde_json::Error> for OTError {
     fn from(error: serde_json::Error) -> Self { ErrorBuilder::new(OTErrorCode::SerdeError).error(error).build() }
 }
 
+impl std::convert::From<Utf8Error> for OTError {
+    fn from(error: Utf8Error) -> Self { ErrorBuilder::new(OTErrorCode::SerdeError).error(error).build() }
+}
+
 #[derive(Debug, Clone)]
 pub enum OTErrorCode {
     IncompatibleLength,

+ 2 - 0
rust-lib/flowy-user/src/protobuf/mod.rs

@@ -1,2 +1,4 @@
+
 mod model;
 pub use model::*;
+        

+ 13 - 13
rust-lib/flowy-user/src/protobuf/model/mod.rs

@@ -1,19 +1,19 @@
-// Auto-generated, do not edit
+// Auto-generated, do not edit 
 
-mod observable;
-pub use observable::*;
+mod observable; 
+pub use observable::*; 
 
-mod user_table;
-pub use user_table::*;
+mod user_table; 
+pub use user_table::*; 
 
-mod errors;
-pub use errors::*;
+mod errors; 
+pub use errors::*; 
 
-mod user_profile;
-pub use user_profile::*;
+mod user_profile; 
+pub use user_profile::*; 
 
-mod event;
-pub use event::*;
+mod event; 
+pub use event::*; 
 
-mod auth;
-pub use auth::*;
+mod auth; 
+pub use auth::*; 

+ 1 - 0
rust-lib/flowy-workspace/Cargo.toml

@@ -13,6 +13,7 @@ flowy-sqlite = { path = "../flowy-sqlite" }
 flowy-infra = { path = "../flowy-infra" }
 flowy-observable = { path = "../flowy-observable" }
 flowy-document = { path = "../flowy-document" }
+flowy-ot = { path = "../flowy-ot" }
 flowy-net = { path = "../flowy-net", features = ["flowy_request"] }
 
 protobuf = {version = "2.18.0"}

+ 16 - 0
rust-lib/flowy-workspace/src/entities/view/parser/delta_data.rs

@@ -0,0 +1,16 @@
+use flowy_ot::core::Delta;
+
+#[derive(Debug)]
+pub struct DeltaData(pub Delta);
+
+impl DeltaData {
+    pub fn parse(data: Vec<u8>) -> Result<DeltaData, String> {
+        let delta = Delta::from_bytes(data).map_err(|e| format!("{:?}", e))?;
+
+        Ok(Self(delta))
+    }
+}
+
+impl AsRef<Delta> for DeltaData {
+    fn as_ref(&self) -> &Delta { &self.0 }
+}

+ 2 - 0
rust-lib/flowy-workspace/src/entities/view/parser/mod.rs

@@ -1,8 +1,10 @@
+mod delta_data;
 mod view_desc;
 mod view_id;
 mod view_name;
 mod view_thumbnail;
 
+pub use delta_data::*;
 pub use view_desc::*;
 pub use view_id::*;
 pub use view_name::*;

+ 18 - 10
rust-lib/flowy-workspace/src/entities/view/view_create.rs

@@ -65,7 +65,23 @@ pub struct CreateViewParams {
     pub view_type: ViewType,
 
     #[pb(index = 6)]
-    pub data: String,
+    pub data: Vec<u8>,
+}
+
+const VIEW_DEFAULT_DATA: &str = "[{\"insert\":\"\\n\"}]";
+pub fn default_delta() -> Vec<u8> { VIEW_DEFAULT_DATA.as_bytes().to_vec() }
+
+impl CreateViewParams {
+    pub fn new(belong_to_id: String, name: String, desc: String, view_type: ViewType, thumbnail: String) -> Self {
+        Self {
+            belong_to_id,
+            name,
+            desc,
+            thumbnail,
+            view_type,
+            data: default_delta(),
+        }
+    }
 }
 
 impl TryInto<CreateViewParams> for CreateViewRequest {
@@ -89,15 +105,7 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
             },
         };
 
-        Ok(CreateViewParams {
-            belong_to_id,
-            name,
-            desc: self.desc,
-            thumbnail,
-            view_type: self.view_type,
-            // TODO: replace the placeholder
-            data: "[{\"insert\":\"\\n\"}]".to_owned(),
-        })
+        Ok(CreateViewParams::new(belong_to_id, name, self.desc, self.view_type, thumbnail))
     }
 }
 

+ 40 - 9
rust-lib/flowy-workspace/src/entities/view/view_update.rs

@@ -3,7 +3,7 @@ use crate::{
     errors::{ErrorBuilder, ErrorCode, WorkspaceError},
 };
 use flowy_derive::ProtoBuf;
-use flowy_document::entities::doc::UpdateDocParams;
+use flowy_document::entities::doc::{ApplyChangesetParams, SaveDocParams};
 use std::convert::TryInto;
 
 #[derive(Default, ProtoBuf)]
@@ -111,24 +111,55 @@ impl TryInto<UpdateViewParams> for UpdateViewRequest {
 }
 
 #[derive(Default, ProtoBuf)]
-pub struct UpdateViewDataRequest {
+pub struct SaveViewDataRequest {
     #[pb(index = 1)]
     pub view_id: String,
 
     #[pb(index = 2)]
-    pub data: String,
+    pub data: Vec<u8>,
 }
 
-impl TryInto<UpdateDocParams> for UpdateViewDataRequest {
+impl TryInto<SaveDocParams> for SaveViewDataRequest {
     type Error = WorkspaceError;
 
-    fn try_into(self) -> Result<UpdateDocParams, Self::Error> {
+    fn try_into(self) -> Result<SaveDocParams, Self::Error> {
         let view_id = ViewId::parse(self.view_id)
             .map_err(|e| ErrorBuilder::new(ErrorCode::ViewIdInvalid).msg(e).build())?
             .0;
-        Ok(UpdateDocParams {
-            id: view_id,
-            data: Some(self.data),
-        })
+
+        // Opti: Vec<u8> -> Delta -> Vec<u8>
+        let data = DeltaData::parse(self.data)
+            .map_err(|e| ErrorBuilder::new(ErrorCode::ViewDataInvalid).msg(e).build())?
+            .0
+            .into_bytes();
+
+        Ok(SaveDocParams { id: view_id, data })
+    }
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct ApplyChangesetRequest {
+    #[pb(index = 1)]
+    pub view_id: String,
+
+    #[pb(index = 2)]
+    pub data: Vec<u8>,
+}
+
+impl TryInto<ApplyChangesetParams> for ApplyChangesetRequest {
+    type Error = WorkspaceError;
+
+    fn try_into(self) -> Result<ApplyChangesetParams, Self::Error> {
+        let view_id = ViewId::parse(self.view_id)
+            .map_err(|e| ErrorBuilder::new(ErrorCode::ViewIdInvalid).msg(e).build())?
+            .0;
+
+        // Opti: Vec<u8> -> Delta -> Vec<u8>
+        let data = DeltaData::parse(self.data)
+            .map_err(|e| ErrorBuilder::new(ErrorCode::ViewDataInvalid).msg(e).build())?
+            .0
+            .into_bytes();
+
+        Ok(ApplyChangesetParams { id: view_id, data })
     }
 }

+ 3 - 0
rust-lib/flowy-workspace/src/errors.rs

@@ -57,6 +57,9 @@ pub enum ErrorCode {
     #[display(fmt = "Description of the View is invalid")]
     ViewDescInvalid      = 23,
 
+    #[display(fmt = "View data is invalid")]
+    ViewDataInvalid      = 24,
+
     #[display(fmt = "UserIn is empty")]
     UserIdIsEmpty        = 100,
 

+ 5 - 2
rust-lib/flowy-workspace/src/event.rs

@@ -49,6 +49,9 @@ pub enum WorkspaceEvent {
     #[event(input = "OpenViewRequest", output = "Doc")]
     OpenView          = 205,
 
-    #[event(input = "UpdateViewDataRequest")]
-    UpdateViewData    = 206,
+    #[event(input = "SaveViewDataRequest")]
+    SaveViewData      = 206,
+
+    #[event(input = "ApplyChangesetRequest")]
+    ApplyChangeset    = 207,
 }

+ 15 - 4
rust-lib/flowy-workspace/src/handlers/view_handler.rs

@@ -1,5 +1,6 @@
 use crate::{
     entities::view::{
+        ApplyChangesetRequest,
         CreateViewParams,
         CreateViewRequest,
         DeleteViewParams,
@@ -7,7 +8,7 @@ use crate::{
         OpenViewRequest,
         QueryViewParams,
         QueryViewRequest,
-        UpdateViewDataRequest,
+        SaveViewDataRequest,
         UpdateViewParams,
         UpdateViewRequest,
         View,
@@ -16,7 +17,7 @@ use crate::{
     services::ViewController,
 };
 use flowy_dispatch::prelude::{data_result, Data, DataResult, Unit};
-use flowy_document::entities::doc::{Doc, QueryDocParams, UpdateDocParams};
+use flowy_document::entities::doc::{ApplyChangesetParams, Doc, QueryDocParams, SaveDocParams};
 use std::{convert::TryInto, sync::Arc};
 
 #[tracing::instrument(skip(data, controller), err)]
@@ -56,14 +57,24 @@ pub(crate) async fn update_view_handler(
 }
 #[tracing::instrument(skip(data, controller), err)]
 pub(crate) async fn update_view_data_handler(
-    data: Data<UpdateViewDataRequest>,
+    data: Data<SaveViewDataRequest>,
     controller: Unit<Arc<ViewController>>,
 ) -> Result<(), WorkspaceError> {
-    let params: UpdateDocParams = data.into_inner().try_into()?;
+    let params: SaveDocParams = data.into_inner().try_into()?;
     let _ = controller.update_view_data(params).await?;
     Ok(())
 }
 
+#[tracing::instrument(skip(data, controller), err)]
+pub(crate) async fn apply_changeset_handler(
+    data: Data<ApplyChangesetRequest>,
+    controller: Unit<Arc<ViewController>>,
+) -> Result<(), WorkspaceError> {
+    let params: ApplyChangesetParams = data.into_inner().try_into()?;
+    let _ = controller.apply_changeset(params).await?;
+    Ok(())
+}
+
 #[tracing::instrument(skip(data, controller), err)]
 pub(crate) async fn delete_view_handler(
     data: Data<DeleteViewRequest>,

+ 2 - 1
rust-lib/flowy-workspace/src/module.rs

@@ -67,7 +67,8 @@ pub fn create(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>
         .event(WorkspaceEvent::UpdateView, update_view_handler)
         .event(WorkspaceEvent::DeleteView, delete_view_handler)
         .event(WorkspaceEvent::OpenView, open_view_handler)
-        .event(WorkspaceEvent::UpdateViewData, update_view_data_handler);
+        .event(WorkspaceEvent::SaveViewData, update_view_data_handler)
+        .event(WorkspaceEvent::ApplyChangeset, apply_changeset_handler);
 
     module
 }

+ 2 - 0
rust-lib/flowy-workspace/src/protobuf/mod.rs

@@ -1,2 +1,4 @@
+
 mod model;
 pub use model::*;
+        

+ 51 - 45
rust-lib/flowy-workspace/src/protobuf/model/errors.rs

@@ -227,6 +227,7 @@ pub enum ErrorCode {
     ViewThumbnailInvalid = 21,
     ViewIdInvalid = 22,
     ViewDescInvalid = 23,
+    ViewDataInvalid = 24,
     UserIdIsEmpty = 100,
     UserUnauthorized = 101,
     InternalError = 1000,
@@ -252,6 +253,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             21 => ::std::option::Option::Some(ErrorCode::ViewThumbnailInvalid),
             22 => ::std::option::Option::Some(ErrorCode::ViewIdInvalid),
             23 => ::std::option::Option::Some(ErrorCode::ViewDescInvalid),
+            24 => ::std::option::Option::Some(ErrorCode::ViewDataInvalid),
             100 => ::std::option::Option::Some(ErrorCode::UserIdIsEmpty),
             101 => ::std::option::Option::Some(ErrorCode::UserUnauthorized),
             1000 => ::std::option::Option::Some(ErrorCode::InternalError),
@@ -274,6 +276,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             ErrorCode::ViewThumbnailInvalid,
             ErrorCode::ViewIdInvalid,
             ErrorCode::ViewDescInvalid,
+            ErrorCode::ViewDataInvalid,
             ErrorCode::UserIdIsEmpty,
             ErrorCode::UserUnauthorized,
             ErrorCode::InternalError,
@@ -308,56 +311,59 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"B\n\x0eWorkspaceError\x12\x1e\n\x04code\x18\x01\x20\
     \x01(\x0e2\n.ErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03\
-    msg*\xeb\x02\n\tErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x18\n\x14Worksp\
+    msg*\x80\x03\n\tErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x18\n\x14Worksp\
     aceNameInvalid\x10\x01\x12\x16\n\x12WorkspaceIdInvalid\x10\x02\x12\x18\n\
     \x14AppColorStyleInvalid\x10\x03\x12\x18\n\x14WorkspaceDescInvalid\x10\
     \x04\x12\x1c\n\x18CurrentWorkspaceNotFound\x10\x05\x12\x10\n\x0cAppIdInv\
     alid\x10\n\x12\x12\n\x0eAppNameInvalid\x10\x0b\x12\x13\n\x0fViewNameInva\
     lid\x10\x14\x12\x18\n\x14ViewThumbnailInvalid\x10\x15\x12\x11\n\rViewIdI\
-    nvalid\x10\x16\x12\x13\n\x0fViewDescInvalid\x10\x17\x12\x11\n\rUserIdIsE\
-    mpty\x10d\x12\x14\n\x10UserUnauthorized\x10e\x12\x12\n\rInternalError\
-    \x10\xe8\x07\x12\x13\n\x0eRecordNotFound\x10\xe9\x07J\xc0\x06\n\x06\x12\
-    \x04\0\0\x17\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\
-    \x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x16\n\x0b\n\x04\x04\0\
-    \x02\0\x12\x03\x03\x04\x17\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\x04\r\
-    \n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0e\x12\n\x0c\n\x05\x04\0\x02\0\
-    \x03\x12\x03\x03\x15\x16\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\
-    \x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\
-    \x01\x12\x03\x04\x0b\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x11\
-    \x12\n\n\n\x02\x05\0\x12\x04\x06\0\x17\x01\n\n\n\x03\x05\0\x01\x12\x03\
-    \x06\x05\x0e\n\x0b\n\x04\x05\0\x02\0\x12\x03\x07\x04\x10\n\x0c\n\x05\x05\
-    \0\x02\0\x01\x12\x03\x07\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\
-    \x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x08\x04\x1d\n\x0c\n\x05\x05\0\
-    \x02\x01\x01\x12\x03\x08\x04\x18\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\
-    \x08\x1b\x1c\n\x0b\n\x04\x05\0\x02\x02\x12\x03\t\x04\x1b\n\x0c\n\x05\x05\
-    \0\x02\x02\x01\x12\x03\t\x04\x16\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\
-    \x19\x1a\n\x0b\n\x04\x05\0\x02\x03\x12\x03\n\x04\x1d\n\x0c\n\x05\x05\0\
-    \x02\x03\x01\x12\x03\n\x04\x18\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\
-    \x1b\x1c\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x0b\x04\x1d\n\x0c\n\x05\x05\0\
-    \x02\x04\x01\x12\x03\x0b\x04\x18\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\
-    \x0b\x1b\x1c\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x0c\x04!\n\x0c\n\x05\x05\
-    \0\x02\x05\x01\x12\x03\x0c\x04\x1c\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\
-    \x0c\x1f\x20\n\x0b\n\x04\x05\0\x02\x06\x12\x03\r\x04\x16\n\x0c\n\x05\x05\
-    \0\x02\x06\x01\x12\x03\r\x04\x10\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\
-    \x13\x15\n\x0b\n\x04\x05\0\x02\x07\x12\x03\x0e\x04\x18\n\x0c\n\x05\x05\0\
-    \x02\x07\x01\x12\x03\x0e\x04\x12\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\
-    \x0e\x15\x17\n\x0b\n\x04\x05\0\x02\x08\x12\x03\x0f\x04\x19\n\x0c\n\x05\
-    \x05\0\x02\x08\x01\x12\x03\x0f\x04\x13\n\x0c\n\x05\x05\0\x02\x08\x02\x12\
-    \x03\x0f\x16\x18\n\x0b\n\x04\x05\0\x02\t\x12\x03\x10\x04\x1e\n\x0c\n\x05\
-    \x05\0\x02\t\x01\x12\x03\x10\x04\x18\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\
-    \x10\x1b\x1d\n\x0b\n\x04\x05\0\x02\n\x12\x03\x11\x04\x17\n\x0c\n\x05\x05\
-    \0\x02\n\x01\x12\x03\x11\x04\x11\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\
-    \x14\x16\n\x0b\n\x04\x05\0\x02\x0b\x12\x03\x12\x04\x19\n\x0c\n\x05\x05\0\
-    \x02\x0b\x01\x12\x03\x12\x04\x13\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\
-    \x12\x16\x18\n\x0b\n\x04\x05\0\x02\x0c\x12\x03\x13\x04\x18\n\x0c\n\x05\
-    \x05\0\x02\x0c\x01\x12\x03\x13\x04\x11\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\
-    \x03\x13\x14\x17\n\x0b\n\x04\x05\0\x02\r\x12\x03\x14\x04\x1b\n\x0c\n\x05\
-    \x05\0\x02\r\x01\x12\x03\x14\x04\x14\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\
-    \x14\x17\x1a\n\x0b\n\x04\x05\0\x02\x0e\x12\x03\x15\x04\x19\n\x0c\n\x05\
-    \x05\0\x02\x0e\x01\x12\x03\x15\x04\x11\n\x0c\n\x05\x05\0\x02\x0e\x02\x12\
-    \x03\x15\x14\x18\n\x0b\n\x04\x05\0\x02\x0f\x12\x03\x16\x04\x1a\n\x0c\n\
-    \x05\x05\0\x02\x0f\x01\x12\x03\x16\x04\x12\n\x0c\n\x05\x05\0\x02\x0f\x02\
-    \x12\x03\x16\x15\x19b\x06proto3\
+    nvalid\x10\x16\x12\x13\n\x0fViewDescInvalid\x10\x17\x12\x13\n\x0fViewDat\
+    aInvalid\x10\x18\x12\x11\n\rUserIdIsEmpty\x10d\x12\x14\n\x10UserUnauthor\
+    ized\x10e\x12\x12\n\rInternalError\x10\xe8\x07\x12\x13\n\x0eRecordNotFou\
+    nd\x10\xe9\x07J\xe9\x06\n\x06\x12\x04\0\0\x18\x01\n\x08\n\x01\x0c\x12\
+    \x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\
+    \x12\x03\x02\x08\x16\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x17\n\x0c\n\
+    \x05\x04\0\x02\0\x06\x12\x03\x03\x04\r\n\x0c\n\x05\x04\0\x02\0\x01\x12\
+    \x03\x03\x0e\x12\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x15\x16\n\x0b\n\
+    \x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\x05\x12\
+    \x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\n\x0c\n\
+    \x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\
+    \x18\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x0e\n\x0b\n\x04\x05\0\x02\0\
+    \x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\x04\x0b\n\
+    \x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\
+    \x12\x03\x08\x04\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x18\n\
+    \x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1b\x1c\n\x0b\n\x04\x05\0\x02\
+    \x02\x12\x03\t\x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x16\n\
+    \x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x19\x1a\n\x0b\n\x04\x05\0\x02\x03\
+    \x12\x03\n\x04\x1d\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x18\n\x0c\
+    \n\x05\x05\0\x02\x03\x02\x12\x03\n\x1b\x1c\n\x0b\n\x04\x05\0\x02\x04\x12\
+    \x03\x0b\x04\x1d\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x18\n\x0c\
+    \n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x1b\x1c\n\x0b\n\x04\x05\0\x02\x05\
+    \x12\x03\x0c\x04!\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x1c\n\
+    \x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x1f\x20\n\x0b\n\x04\x05\0\x02\
+    \x06\x12\x03\r\x04\x16\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x10\n\
+    \x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x13\x15\n\x0b\n\x04\x05\0\x02\x07\
+    \x12\x03\x0e\x04\x18\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x12\n\
+    \x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x15\x17\n\x0b\n\x04\x05\0\x02\
+    \x08\x12\x03\x0f\x04\x19\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\
+    \x13\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x16\x18\n\x0b\n\x04\x05\0\
+    \x02\t\x12\x03\x10\x04\x1e\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x10\x04\
+    \x18\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x10\x1b\x1d\n\x0b\n\x04\x05\0\
+    \x02\n\x12\x03\x11\x04\x17\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\x11\x04\
+    \x11\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\x14\x16\n\x0b\n\x04\x05\0\
+    \x02\x0b\x12\x03\x12\x04\x19\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\
+    \x04\x13\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x12\x16\x18\n\x0b\n\x04\
+    \x05\0\x02\x0c\x12\x03\x13\x04\x19\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\
+    \x13\x04\x13\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x13\x16\x18\n\x0b\n\
+    \x04\x05\0\x02\r\x12\x03\x14\x04\x18\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\
+    \x14\x04\x11\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x14\x14\x17\n\x0b\n\x04\
+    \x05\0\x02\x0e\x12\x03\x15\x04\x1b\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\
+    \x15\x04\x14\n\x0c\n\x05\x05\0\x02\x0e\x02\x12\x03\x15\x17\x1a\n\x0b\n\
+    \x04\x05\0\x02\x0f\x12\x03\x16\x04\x19\n\x0c\n\x05\x05\0\x02\x0f\x01\x12\
+    \x03\x16\x04\x11\n\x0c\n\x05\x05\0\x02\x0f\x02\x12\x03\x16\x14\x18\n\x0b\
+    \n\x04\x05\0\x02\x10\x12\x03\x17\x04\x1a\n\x0c\n\x05\x05\0\x02\x10\x01\
+    \x12\x03\x17\x04\x12\n\x0c\n\x05\x05\0\x02\x10\x02\x12\x03\x17\x15\x19b\
+    \x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 45 - 40
rust-lib/flowy-workspace/src/protobuf/model/event.rs

@@ -40,7 +40,8 @@ pub enum WorkspaceEvent {
     UpdateView = 203,
     DeleteView = 204,
     OpenView = 205,
-    UpdateViewData = 206,
+    SaveViewData = 206,
+    ApplyChangeset = 207,
 }
 
 impl ::protobuf::ProtobufEnum for WorkspaceEvent {
@@ -65,7 +66,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
             203 => ::std::option::Option::Some(WorkspaceEvent::UpdateView),
             204 => ::std::option::Option::Some(WorkspaceEvent::DeleteView),
             205 => ::std::option::Option::Some(WorkspaceEvent::OpenView),
-            206 => ::std::option::Option::Some(WorkspaceEvent::UpdateViewData),
+            206 => ::std::option::Option::Some(WorkspaceEvent::SaveViewData),
+            207 => ::std::option::Option::Some(WorkspaceEvent::ApplyChangeset),
             _ => ::std::option::Option::None
         }
     }
@@ -87,7 +89,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
             WorkspaceEvent::UpdateView,
             WorkspaceEvent::DeleteView,
             WorkspaceEvent::OpenView,
-            WorkspaceEvent::UpdateViewData,
+            WorkspaceEvent::SaveViewData,
+            WorkspaceEvent::ApplyChangeset,
         ];
         values
     }
@@ -116,49 +119,51 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*\xae\x02\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\
+    \n\x0bevent.proto*\xc1\x02\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\
     ace\x10\0\x12\x14\n\x10ReadCurWorkspace\x10\x01\x12\x12\n\x0eReadWorkspa\
     ces\x10\x02\x12\x13\n\x0fDeleteWorkspace\x10\x03\x12\x11\n\rOpenWorkspac\
     e\x10\x04\x12\x15\n\x11ReadWorkspaceApps\x10\x05\x12\r\n\tCreateApp\x10e\
     \x12\r\n\tDeleteApp\x10f\x12\x0b\n\x07ReadApp\x10g\x12\r\n\tUpdateApp\
     \x10h\x12\x0f\n\nCreateView\x10\xc9\x01\x12\r\n\x08ReadView\x10\xca\x01\
     \x12\x0f\n\nUpdateView\x10\xcb\x01\x12\x0f\n\nDeleteView\x10\xcc\x01\x12\
-    \r\n\x08OpenView\x10\xcd\x01\x12\x13\n\x0eUpdateViewData\x10\xce\x01J\
-    \xba\x05\n\x06\x12\x04\0\0\x13\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\
-    \x02\x05\0\x12\x04\x02\0\x13\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\
-    \n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\
-    \x12\x03\x03\x04\x13\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x16\x17\n\
-    \x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x19\n\x0c\n\x05\x05\0\x02\x01\
-    \x01\x12\x03\x04\x04\x14\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\x17\
-    \x18\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\x17\n\x0c\n\x05\x05\0\x02\
-    \x02\x01\x12\x03\x05\x04\x12\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\
-    \x15\x16\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\x04\x18\n\x0c\n\x05\x05\0\
-    \x02\x03\x01\x12\x03\x06\x04\x13\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\
-    \x06\x16\x17\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x07\x04\x16\n\x0c\n\x05\
-    \x05\0\x02\x04\x01\x12\x03\x07\x04\x11\n\x0c\n\x05\x05\0\x02\x04\x02\x12\
-    \x03\x07\x14\x15\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x08\x04\x1a\n\x0c\n\
-    \x05\x05\0\x02\x05\x01\x12\x03\x08\x04\x15\n\x0c\n\x05\x05\0\x02\x05\x02\
-    \x12\x03\x08\x18\x19\n\x0b\n\x04\x05\0\x02\x06\x12\x03\t\x04\x14\n\x0c\n\
-    \x05\x05\0\x02\x06\x01\x12\x03\t\x04\r\n\x0c\n\x05\x05\0\x02\x06\x02\x12\
-    \x03\t\x10\x13\n\x0b\n\x04\x05\0\x02\x07\x12\x03\n\x04\x14\n\x0c\n\x05\
-    \x05\0\x02\x07\x01\x12\x03\n\x04\r\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\
-    \n\x10\x13\n\x0b\n\x04\x05\0\x02\x08\x12\x03\x0b\x04\x12\n\x0c\n\x05\x05\
-    \0\x02\x08\x01\x12\x03\x0b\x04\x0b\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\
-    \x0b\x0e\x11\n\x0b\n\x04\x05\0\x02\t\x12\x03\x0c\x04\x14\n\x0c\n\x05\x05\
-    \0\x02\t\x01\x12\x03\x0c\x04\r\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x0c\
-    \x10\x13\n\x0b\n\x04\x05\0\x02\n\x12\x03\r\x04\x15\n\x0c\n\x05\x05\0\x02\
-    \n\x01\x12\x03\r\x04\x0e\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\r\x11\x14\n\
-    \x0b\n\x04\x05\0\x02\x0b\x12\x03\x0e\x04\x13\n\x0c\n\x05\x05\0\x02\x0b\
-    \x01\x12\x03\x0e\x04\x0c\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x0e\x0f\
-    \x12\n\x0b\n\x04\x05\0\x02\x0c\x12\x03\x0f\x04\x15\n\x0c\n\x05\x05\0\x02\
-    \x0c\x01\x12\x03\x0f\x04\x0e\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x0f\
-    \x11\x14\n\x0b\n\x04\x05\0\x02\r\x12\x03\x10\x04\x15\n\x0c\n\x05\x05\0\
-    \x02\r\x01\x12\x03\x10\x04\x0e\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x10\
-    \x11\x14\n\x0b\n\x04\x05\0\x02\x0e\x12\x03\x11\x04\x13\n\x0c\n\x05\x05\0\
-    \x02\x0e\x01\x12\x03\x11\x04\x0c\n\x0c\n\x05\x05\0\x02\x0e\x02\x12\x03\
-    \x11\x0f\x12\n\x0b\n\x04\x05\0\x02\x0f\x12\x03\x12\x04\x19\n\x0c\n\x05\
-    \x05\0\x02\x0f\x01\x12\x03\x12\x04\x12\n\x0c\n\x05\x05\0\x02\x0f\x02\x12\
-    \x03\x12\x15\x18b\x06proto3\
+    \r\n\x08OpenView\x10\xcd\x01\x12\x11\n\x0cSaveViewData\x10\xce\x01\x12\
+    \x13\n\x0eApplyChangeset\x10\xcf\x01J\xe3\x05\n\x06\x12\x04\0\0\x14\x01\
+    \n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x14\x01\n\
+    \n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\x0b\n\x04\x05\0\x02\0\x12\x03\
+    \x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x13\n\x0c\n\x05\
+    \x05\0\x02\0\x02\x12\x03\x03\x16\x17\n\x0b\n\x04\x05\0\x02\x01\x12\x03\
+    \x04\x04\x19\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x14\n\x0c\n\
+    \x05\x05\0\x02\x01\x02\x12\x03\x04\x17\x18\n\x0b\n\x04\x05\0\x02\x02\x12\
+    \x03\x05\x04\x17\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x12\n\x0c\
+    \n\x05\x05\0\x02\x02\x02\x12\x03\x05\x15\x16\n\x0b\n\x04\x05\0\x02\x03\
+    \x12\x03\x06\x04\x18\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x13\n\
+    \x0c\n\x05\x05\0\x02\x03\x02\x12\x03\x06\x16\x17\n\x0b\n\x04\x05\0\x02\
+    \x04\x12\x03\x07\x04\x16\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\
+    \x11\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x07\x14\x15\n\x0b\n\x04\x05\0\
+    \x02\x05\x12\x03\x08\x04\x1a\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\
+    \x04\x15\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x08\x18\x19\n\x0b\n\x04\
+    \x05\0\x02\x06\x12\x03\t\x04\x14\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\
+    \x04\r\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\t\x10\x13\n\x0b\n\x04\x05\0\
+    \x02\x07\x12\x03\n\x04\x14\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\n\x04\r\
+    \n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\n\x10\x13\n\x0b\n\x04\x05\0\x02\
+    \x08\x12\x03\x0b\x04\x12\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0b\x04\
+    \x0b\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x0e\x11\n\x0b\n\x04\x05\0\
+    \x02\t\x12\x03\x0c\x04\x14\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x0c\x04\r\
+    \n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x0c\x10\x13\n\x0b\n\x04\x05\0\x02\n\
+    \x12\x03\r\x04\x15\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\r\x04\x0e\n\x0c\n\
+    \x05\x05\0\x02\n\x02\x12\x03\r\x11\x14\n\x0b\n\x04\x05\0\x02\x0b\x12\x03\
+    \x0e\x04\x13\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x0e\x04\x0c\n\x0c\n\
+    \x05\x05\0\x02\x0b\x02\x12\x03\x0e\x0f\x12\n\x0b\n\x04\x05\0\x02\x0c\x12\
+    \x03\x0f\x04\x15\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\x0f\x04\x0e\n\x0c\
+    \n\x05\x05\0\x02\x0c\x02\x12\x03\x0f\x11\x14\n\x0b\n\x04\x05\0\x02\r\x12\
+    \x03\x10\x04\x15\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\x10\x04\x0e\n\x0c\n\
+    \x05\x05\0\x02\r\x02\x12\x03\x10\x11\x14\n\x0b\n\x04\x05\0\x02\x0e\x12\
+    \x03\x11\x04\x13\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\x11\x04\x0c\n\x0c\
+    \n\x05\x05\0\x02\x0e\x02\x12\x03\x11\x0f\x12\n\x0b\n\x04\x05\0\x02\x0f\
+    \x12\x03\x12\x04\x17\n\x0c\n\x05\x05\0\x02\x0f\x01\x12\x03\x12\x04\x10\n\
+    \x0c\n\x05\x05\0\x02\x0f\x02\x12\x03\x12\x13\x16\n\x0b\n\x04\x05\0\x02\
+    \x10\x12\x03\x13\x04\x19\n\x0c\n\x05\x05\0\x02\x10\x01\x12\x03\x13\x04\
+    \x12\n\x0c\n\x05\x05\0\x02\x10\x02\x12\x03\x13\x15\x18b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 33 - 33
rust-lib/flowy-workspace/src/protobuf/model/mod.rs

@@ -1,49 +1,49 @@
-// Auto-generated, do not edit
+// Auto-generated, do not edit 
 
-mod view_update;
-pub use view_update::*;
+mod view_update; 
+pub use view_update::*; 
 
-mod view_delete;
-pub use view_delete::*;
+mod view_delete; 
+pub use view_delete::*; 
 
-mod app_query;
-pub use app_query::*;
+mod app_query; 
+pub use app_query::*; 
 
-mod workspace_delete;
-pub use workspace_delete::*;
+mod workspace_delete; 
+pub use workspace_delete::*; 
 
-mod observable;
-pub use observable::*;
+mod observable; 
+pub use observable::*; 
 
-mod errors;
-pub use errors::*;
+mod errors; 
+pub use errors::*; 
 
-mod workspace_update;
-pub use workspace_update::*;
+mod workspace_update; 
+pub use workspace_update::*; 
 
-mod app_create;
-pub use app_create::*;
+mod app_create; 
+pub use app_create::*; 
 
-mod workspace_query;
-pub use workspace_query::*;
+mod workspace_query; 
+pub use workspace_query::*; 
 
-mod event;
-pub use event::*;
+mod event; 
+pub use event::*; 
 
-mod view_create;
-pub use view_create::*;
+mod view_create; 
+pub use view_create::*; 
 
-mod workspace_user_detail;
-pub use workspace_user_detail::*;
+mod workspace_user_detail; 
+pub use workspace_user_detail::*; 
 
-mod workspace_create;
-pub use workspace_create::*;
+mod workspace_create; 
+pub use workspace_create::*; 
 
-mod app_update;
-pub use app_update::*;
+mod app_update; 
+pub use app_update::*; 
 
-mod view_query;
-pub use view_query::*;
+mod view_query; 
+pub use view_query::*; 
 
-mod app_delete;
-pub use app_delete::*;
+mod app_delete; 
+pub use app_delete::*; 

+ 15 - 15
rust-lib/flowy-workspace/src/protobuf/model/view_create.rs

@@ -387,7 +387,7 @@ pub struct CreateViewParams {
     pub desc: ::std::string::String,
     pub thumbnail: ::std::string::String,
     pub view_type: ViewType,
-    pub data: ::std::string::String,
+    pub data: ::std::vec::Vec<u8>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -523,10 +523,10 @@ impl CreateViewParams {
         self.view_type = v;
     }
 
-    // string data = 6;
+    // bytes data = 6;
 
 
-    pub fn get_data(&self) -> &str {
+    pub fn get_data(&self) -> &[u8] {
         &self.data
     }
     pub fn clear_data(&mut self) {
@@ -534,19 +534,19 @@ impl CreateViewParams {
     }
 
     // Param is passed by value, moved
-    pub fn set_data(&mut self, v: ::std::string::String) {
+    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
         self.data = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_data(&mut self) -> &mut ::std::string::String {
+    pub fn mut_data(&mut self) -> &mut ::std::vec::Vec<u8> {
         &mut self.data
     }
 
     // Take field
-    pub fn take_data(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.data, ::std::string::String::new())
+    pub fn take_data(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.data, ::std::vec::Vec::new())
     }
 }
 
@@ -575,7 +575,7 @@ impl ::protobuf::Message for CreateViewParams {
                     ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.view_type, 5, &mut self.unknown_fields)?
                 },
                 6 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -605,7 +605,7 @@ impl ::protobuf::Message for CreateViewParams {
             my_size += ::protobuf::rt::enum_size(5, self.view_type);
         }
         if !self.data.is_empty() {
-            my_size += ::protobuf::rt::string_size(6, &self.data);
+            my_size += ::protobuf::rt::bytes_size(6, &self.data);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -629,7 +629,7 @@ impl ::protobuf::Message for CreateViewParams {
             os.write_enum(5, ::protobuf::ProtobufEnum::value(&self.view_type))?;
         }
         if !self.data.is_empty() {
-            os.write_string(6, &self.data)?;
+            os.write_bytes(6, &self.data)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -694,7 +694,7 @@ impl ::protobuf::Message for CreateViewParams {
                 |m: &CreateViewParams| { &m.view_type },
                 |m: &mut CreateViewParams| { &mut m.view_type },
             ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
                 "data",
                 |m: &CreateViewParams| { &m.data },
                 |m: &mut CreateViewParams| { &mut m.data },
@@ -1441,7 +1441,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \nbelongToId\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04\
     desc\x18\x03\x20\x01(\tR\x04desc\x12\x1c\n\tthumbnail\x18\x04\x20\x01(\t\
     R\tthumbnail\x12&\n\tview_type\x18\x05\x20\x01(\x0e2\t.ViewTypeR\x08view\
-    Type\x12\x12\n\x04data\x18\x06\x20\x01(\tR\x04data\"\x97\x02\n\x04View\
+    Type\x12\x12\n\x04data\x18\x06\x20\x01(\x0cR\x04data\"\x97\x02\n\x04View\
     \x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x20\n\x0cbelong_to_id\x18\
     \x02\x20\x01(\tR\nbelongToId\x12\x12\n\x04name\x18\x03\x20\x01(\tR\x04na\
     me\x12\x12\n\x04desc\x18\x04\x20\x01(\tR\x04desc\x12&\n\tview_type\x18\
@@ -1483,9 +1483,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x12\x03\x0e\x04\x1b\n\x0c\n\x05\x04\x01\x02\x04\x06\x12\x03\x0e\x04\x0c\
     \n\x0c\n\x05\x04\x01\x02\x04\x01\x12\x03\x0e\r\x16\n\x0c\n\x05\x04\x01\
     \x02\x04\x03\x12\x03\x0e\x19\x1a\n\x0b\n\x04\x04\x01\x02\x05\x12\x03\x0f\
-    \x04\x14\n\x0c\n\x05\x04\x01\x02\x05\x05\x12\x03\x0f\x04\n\n\x0c\n\x05\
-    \x04\x01\x02\x05\x01\x12\x03\x0f\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x05\x03\
-    \x12\x03\x0f\x12\x13\n\n\n\x02\x04\x02\x12\x04\x11\0\x1b\x01\n\n\n\x03\
+    \x04\x13\n\x0c\n\x05\x04\x01\x02\x05\x05\x12\x03\x0f\x04\t\n\x0c\n\x05\
+    \x04\x01\x02\x05\x01\x12\x03\x0f\n\x0e\n\x0c\n\x05\x04\x01\x02\x05\x03\
+    \x12\x03\x0f\x11\x12\n\n\n\x02\x04\x02\x12\x04\x11\0\x1b\x01\n\n\n\x03\
     \x04\x02\x01\x12\x03\x11\x08\x0c\n\x0b\n\x04\x04\x02\x02\0\x12\x03\x12\
     \x04\x12\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x12\x04\n\n\x0c\n\x05\x04\
     \x02\x02\0\x01\x12\x03\x12\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\

+ 290 - 80
rust-lib/flowy-workspace/src/protobuf/model/view_update.rs

@@ -944,23 +944,23 @@ impl ::protobuf::reflect::ProtobufValue for UpdateViewParams {
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct UpdateViewDataRequest {
+pub struct SaveViewDataRequest {
     // message fields
     pub view_id: ::std::string::String,
-    pub data: ::std::string::String,
+    pub data: ::std::vec::Vec<u8>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a UpdateViewDataRequest {
-    fn default() -> &'a UpdateViewDataRequest {
-        <UpdateViewDataRequest as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a SaveViewDataRequest {
+    fn default() -> &'a SaveViewDataRequest {
+        <SaveViewDataRequest as ::protobuf::Message>::default_instance()
     }
 }
 
-impl UpdateViewDataRequest {
-    pub fn new() -> UpdateViewDataRequest {
+impl SaveViewDataRequest {
+    pub fn new() -> SaveViewDataRequest {
         ::std::default::Default::default()
     }
 
@@ -990,10 +990,10 @@ impl UpdateViewDataRequest {
         ::std::mem::replace(&mut self.view_id, ::std::string::String::new())
     }
 
-    // string data = 2;
+    // bytes data = 2;
 
 
-    pub fn get_data(&self) -> &str {
+    pub fn get_data(&self) -> &[u8] {
         &self.data
     }
     pub fn clear_data(&mut self) {
@@ -1001,23 +1001,23 @@ impl UpdateViewDataRequest {
     }
 
     // Param is passed by value, moved
-    pub fn set_data(&mut self, v: ::std::string::String) {
+    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
         self.data = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_data(&mut self) -> &mut ::std::string::String {
+    pub fn mut_data(&mut self) -> &mut ::std::vec::Vec<u8> {
         &mut self.data
     }
 
     // Take field
-    pub fn take_data(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.data, ::std::string::String::new())
+    pub fn take_data(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.data, ::std::vec::Vec::new())
     }
 }
 
-impl ::protobuf::Message for UpdateViewDataRequest {
+impl ::protobuf::Message for SaveViewDataRequest {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -1030,7 +1030,7 @@ impl ::protobuf::Message for UpdateViewDataRequest {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.view_id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -1048,7 +1048,7 @@ impl ::protobuf::Message for UpdateViewDataRequest {
             my_size += ::protobuf::rt::string_size(1, &self.view_id);
         }
         if !self.data.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.data);
+            my_size += ::protobuf::rt::bytes_size(2, &self.data);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -1060,7 +1060,7 @@ impl ::protobuf::Message for UpdateViewDataRequest {
             os.write_string(1, &self.view_id)?;
         }
         if !self.data.is_empty() {
-            os.write_string(2, &self.data)?;
+            os.write_bytes(2, &self.data)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -1092,8 +1092,8 @@ impl ::protobuf::Message for UpdateViewDataRequest {
         Self::descriptor_static()
     }
 
-    fn new() -> UpdateViewDataRequest {
-        UpdateViewDataRequest::new()
+    fn new() -> SaveViewDataRequest {
+        SaveViewDataRequest::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -1102,29 +1102,230 @@ impl ::protobuf::Message for UpdateViewDataRequest {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "view_id",
-                |m: &UpdateViewDataRequest| { &m.view_id },
-                |m: &mut UpdateViewDataRequest| { &mut m.view_id },
+                |m: &SaveViewDataRequest| { &m.view_id },
+                |m: &mut SaveViewDataRequest| { &mut m.view_id },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
+                "data",
+                |m: &SaveViewDataRequest| { &m.data },
+                |m: &mut SaveViewDataRequest| { &mut m.data },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SaveViewDataRequest>(
+                "SaveViewDataRequest",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static SaveViewDataRequest {
+        static instance: ::protobuf::rt::LazyV2<SaveViewDataRequest> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SaveViewDataRequest::new)
+    }
+}
+
+impl ::protobuf::Clear for SaveViewDataRequest {
+    fn clear(&mut self) {
+        self.view_id.clear();
+        self.data.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for SaveViewDataRequest {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for SaveViewDataRequest {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct ApplyChangesetRequest {
+    // message fields
+    pub view_id: ::std::string::String,
+    pub data: ::std::vec::Vec<u8>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a ApplyChangesetRequest {
+    fn default() -> &'a ApplyChangesetRequest {
+        <ApplyChangesetRequest as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl ApplyChangesetRequest {
+    pub fn new() -> ApplyChangesetRequest {
+        ::std::default::Default::default()
+    }
+
+    // string view_id = 1;
+
+
+    pub fn get_view_id(&self) -> &str {
+        &self.view_id
+    }
+    pub fn clear_view_id(&mut self) {
+        self.view_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_view_id(&mut self, v: ::std::string::String) {
+        self.view_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_view_id(&mut self) -> &mut ::std::string::String {
+        &mut self.view_id
+    }
+
+    // Take field
+    pub fn take_view_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.view_id, ::std::string::String::new())
+    }
+
+    // bytes data = 2;
+
+
+    pub fn get_data(&self) -> &[u8] {
+        &self.data
+    }
+    pub fn clear_data(&mut self) {
+        self.data.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
+        self.data = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_data(&mut self) -> &mut ::std::vec::Vec<u8> {
+        &mut self.data
+    }
+
+    // Take field
+    pub fn take_data(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.data, ::std::vec::Vec::new())
+    }
+}
+
+impl ::protobuf::Message for ApplyChangesetRequest {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.view_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.view_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.view_id);
+        }
+        if !self.data.is_empty() {
+            my_size += ::protobuf::rt::bytes_size(2, &self.data);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.view_id.is_empty() {
+            os.write_string(1, &self.view_id)?;
+        }
+        if !self.data.is_empty() {
+            os.write_bytes(2, &self.data)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> ApplyChangesetRequest {
+        ApplyChangesetRequest::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "view_id",
+                |m: &ApplyChangesetRequest| { &m.view_id },
+                |m: &mut ApplyChangesetRequest| { &mut m.view_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
                 "data",
-                |m: &UpdateViewDataRequest| { &m.data },
-                |m: &mut UpdateViewDataRequest| { &mut m.data },
+                |m: &ApplyChangesetRequest| { &m.data },
+                |m: &mut ApplyChangesetRequest| { &mut m.data },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UpdateViewDataRequest>(
-                "UpdateViewDataRequest",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<ApplyChangesetRequest>(
+                "ApplyChangesetRequest",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static UpdateViewDataRequest {
-        static instance: ::protobuf::rt::LazyV2<UpdateViewDataRequest> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(UpdateViewDataRequest::new)
+    fn default_instance() -> &'static ApplyChangesetRequest {
+        static instance: ::protobuf::rt::LazyV2<ApplyChangesetRequest> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(ApplyChangesetRequest::new)
     }
 }
 
-impl ::protobuf::Clear for UpdateViewDataRequest {
+impl ::protobuf::Clear for ApplyChangesetRequest {
     fn clear(&mut self) {
         self.view_id.clear();
         self.data.clear();
@@ -1132,13 +1333,13 @@ impl ::protobuf::Clear for UpdateViewDataRequest {
     }
 }
 
-impl ::std::fmt::Debug for UpdateViewDataRequest {
+impl ::std::fmt::Debug for ApplyChangesetRequest {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for UpdateViewDataRequest {
+impl ::protobuf::reflect::ProtobufValue for ApplyChangesetRequest {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -1156,55 +1357,64 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x03\x20\x01(\tH\x01R\x04desc\x12\x1e\n\tthumbnail\x18\x04\x20\x01(\tH\
     \x02R\tthumbnail\x12\x1b\n\x08is_trash\x18\x05\x20\x01(\x08H\x03R\x07isT\
     rashB\r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x12\n\x10one_of_thumbnailB\
-    \x11\n\x0fone_of_is_trash\"D\n\x15UpdateViewDataRequest\x12\x17\n\x07vie\
-    w_id\x18\x01\x20\x01(\tR\x06viewId\x12\x12\n\x04data\x18\x02\x20\x01(\tR\
-    \x04dataJ\xc6\x07\n\x06\x12\x04\0\0\x13\x01\n\x08\n\x01\x0c\x12\x03\0\0\
-    \x12\n\n\n\x02\x04\0\x12\x04\x02\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\
-    \x02\x08\x19\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x17\n\x0c\n\x05\x04\
-    \0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\
-    \x0b\x12\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x15\x16\n\x0b\n\x04\x04\
-    \0\x08\0\x12\x03\x04\x04*\n\x0c\n\x05\x04\0\x08\0\x01\x12\x03\x04\n\x15\
-    \n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x18(\n\x0c\n\x05\x04\0\x02\x01\
-    \x05\x12\x03\x04\x18\x1e\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x1f#\
-    \n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04&'\n\x0b\n\x04\x04\0\x08\x01\
-    \x12\x03\x05\x04*\n\x0c\n\x05\x04\0\x08\x01\x01\x12\x03\x05\n\x15\n\x0b\
-    \n\x04\x04\0\x02\x02\x12\x03\x05\x18(\n\x0c\n\x05\x04\0\x02\x02\x05\x12\
-    \x03\x05\x18\x1e\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x05\x1f#\n\x0c\n\
-    \x05\x04\0\x02\x02\x03\x12\x03\x05&'\n\x0b\n\x04\x04\0\x08\x02\x12\x03\
-    \x06\x044\n\x0c\n\x05\x04\0\x08\x02\x01\x12\x03\x06\n\x1a\n\x0b\n\x04\
-    \x04\0\x02\x03\x12\x03\x06\x1d2\n\x0c\n\x05\x04\0\x02\x03\x05\x12\x03\
-    \x06\x1d#\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06$-\n\x0c\n\x05\x04\0\
-    \x02\x03\x03\x12\x03\x0601\n\x0b\n\x04\x04\0\x08\x03\x12\x03\x07\x040\n\
-    \x0c\n\x05\x04\0\x08\x03\x01\x12\x03\x07\n\x19\n\x0b\n\x04\x04\0\x02\x04\
-    \x12\x03\x07\x1c.\n\x0c\n\x05\x04\0\x02\x04\x05\x12\x03\x07\x1c\x20\n\
-    \x0c\n\x05\x04\0\x02\x04\x01\x12\x03\x07!)\n\x0c\n\x05\x04\0\x02\x04\x03\
-    \x12\x03\x07,-\n\n\n\x02\x04\x01\x12\x04\t\0\x0f\x01\n\n\n\x03\x04\x01\
-    \x01\x12\x03\t\x08\x18\n\x0b\n\x04\x04\x01\x02\0\x12\x03\n\x04\x17\n\x0c\
-    \n\x05\x04\x01\x02\0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\
-    \x12\x03\n\x0b\x12\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\n\x15\x16\n\x0b\
-    \n\x04\x04\x01\x08\0\x12\x03\x0b\x04*\n\x0c\n\x05\x04\x01\x08\0\x01\x12\
-    \x03\x0b\n\x15\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\x0b\x18(\n\x0c\n\x05\
-    \x04\x01\x02\x01\x05\x12\x03\x0b\x18\x1e\n\x0c\n\x05\x04\x01\x02\x01\x01\
-    \x12\x03\x0b\x1f#\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x0b&'\n\x0b\n\
-    \x04\x04\x01\x08\x01\x12\x03\x0c\x04*\n\x0c\n\x05\x04\x01\x08\x01\x01\
-    \x12\x03\x0c\n\x15\n\x0b\n\x04\x04\x01\x02\x02\x12\x03\x0c\x18(\n\x0c\n\
-    \x05\x04\x01\x02\x02\x05\x12\x03\x0c\x18\x1e\n\x0c\n\x05\x04\x01\x02\x02\
-    \x01\x12\x03\x0c\x1f#\n\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\x0c&'\n\
-    \x0b\n\x04\x04\x01\x08\x02\x12\x03\r\x044\n\x0c\n\x05\x04\x01\x08\x02\
-    \x01\x12\x03\r\n\x1a\n\x0b\n\x04\x04\x01\x02\x03\x12\x03\r\x1d2\n\x0c\n\
-    \x05\x04\x01\x02\x03\x05\x12\x03\r\x1d#\n\x0c\n\x05\x04\x01\x02\x03\x01\
-    \x12\x03\r$-\n\x0c\n\x05\x04\x01\x02\x03\x03\x12\x03\r01\n\x0b\n\x04\x04\
-    \x01\x08\x03\x12\x03\x0e\x040\n\x0c\n\x05\x04\x01\x08\x03\x01\x12\x03\
-    \x0e\n\x19\n\x0b\n\x04\x04\x01\x02\x04\x12\x03\x0e\x1c.\n\x0c\n\x05\x04\
-    \x01\x02\x04\x05\x12\x03\x0e\x1c\x20\n\x0c\n\x05\x04\x01\x02\x04\x01\x12\
-    \x03\x0e!)\n\x0c\n\x05\x04\x01\x02\x04\x03\x12\x03\x0e,-\n\n\n\x02\x04\
-    \x02\x12\x04\x10\0\x13\x01\n\n\n\x03\x04\x02\x01\x12\x03\x10\x08\x1d\n\
-    \x0b\n\x04\x04\x02\x02\0\x12\x03\x11\x04\x17\n\x0c\n\x05\x04\x02\x02\0\
-    \x05\x12\x03\x11\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x11\x0b\x12\
-    \n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x11\x15\x16\n\x0b\n\x04\x04\x02\
-    \x02\x01\x12\x03\x12\x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x12\
-    \x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x12\x0b\x0f\n\x0c\n\x05\
-    \x04\x02\x02\x01\x03\x12\x03\x12\x12\x13b\x06proto3\
+    \x11\n\x0fone_of_is_trash\"B\n\x13SaveViewDataRequest\x12\x17\n\x07view_\
+    id\x18\x01\x20\x01(\tR\x06viewId\x12\x12\n\x04data\x18\x02\x20\x01(\x0cR\
+    \x04data\"D\n\x15ApplyChangesetRequest\x12\x17\n\x07view_id\x18\x01\x20\
+    \x01(\tR\x06viewId\x12\x12\n\x04data\x18\x02\x20\x01(\x0cR\x04dataJ\xcc\
+    \x08\n\x06\x12\x04\0\0\x17\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
+    \x04\0\x12\x04\x02\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x19\n\
+    \x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x17\n\x0c\n\x05\x04\0\x02\0\x05\
+    \x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x12\n\x0c\
+    \n\x05\x04\0\x02\0\x03\x12\x03\x03\x15\x16\n\x0b\n\x04\x04\0\x08\0\x12\
+    \x03\x04\x04*\n\x0c\n\x05\x04\0\x08\0\x01\x12\x03\x04\n\x15\n\x0b\n\x04\
+    \x04\0\x02\x01\x12\x03\x04\x18(\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\
+    \x04\x18\x1e\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x1f#\n\x0c\n\x05\
+    \x04\0\x02\x01\x03\x12\x03\x04&'\n\x0b\n\x04\x04\0\x08\x01\x12\x03\x05\
+    \x04*\n\x0c\n\x05\x04\0\x08\x01\x01\x12\x03\x05\n\x15\n\x0b\n\x04\x04\0\
+    \x02\x02\x12\x03\x05\x18(\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x05\x18\
+    \x1e\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x05\x1f#\n\x0c\n\x05\x04\0\
+    \x02\x02\x03\x12\x03\x05&'\n\x0b\n\x04\x04\0\x08\x02\x12\x03\x06\x044\n\
+    \x0c\n\x05\x04\0\x08\x02\x01\x12\x03\x06\n\x1a\n\x0b\n\x04\x04\0\x02\x03\
+    \x12\x03\x06\x1d2\n\x0c\n\x05\x04\0\x02\x03\x05\x12\x03\x06\x1d#\n\x0c\n\
+    \x05\x04\0\x02\x03\x01\x12\x03\x06$-\n\x0c\n\x05\x04\0\x02\x03\x03\x12\
+    \x03\x0601\n\x0b\n\x04\x04\0\x08\x03\x12\x03\x07\x040\n\x0c\n\x05\x04\0\
+    \x08\x03\x01\x12\x03\x07\n\x19\n\x0b\n\x04\x04\0\x02\x04\x12\x03\x07\x1c\
+    .\n\x0c\n\x05\x04\0\x02\x04\x05\x12\x03\x07\x1c\x20\n\x0c\n\x05\x04\0\
+    \x02\x04\x01\x12\x03\x07!)\n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\x07,-\n\
+    \n\n\x02\x04\x01\x12\x04\t\0\x0f\x01\n\n\n\x03\x04\x01\x01\x12\x03\t\x08\
+    \x18\n\x0b\n\x04\x04\x01\x02\0\x12\x03\n\x04\x17\n\x0c\n\x05\x04\x01\x02\
+    \0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\n\x0b\x12\n\
+    \x0c\n\x05\x04\x01\x02\0\x03\x12\x03\n\x15\x16\n\x0b\n\x04\x04\x01\x08\0\
+    \x12\x03\x0b\x04*\n\x0c\n\x05\x04\x01\x08\0\x01\x12\x03\x0b\n\x15\n\x0b\
+    \n\x04\x04\x01\x02\x01\x12\x03\x0b\x18(\n\x0c\n\x05\x04\x01\x02\x01\x05\
+    \x12\x03\x0b\x18\x1e\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x0b\x1f#\n\
+    \x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x0b&'\n\x0b\n\x04\x04\x01\x08\x01\
+    \x12\x03\x0c\x04*\n\x0c\n\x05\x04\x01\x08\x01\x01\x12\x03\x0c\n\x15\n\
+    \x0b\n\x04\x04\x01\x02\x02\x12\x03\x0c\x18(\n\x0c\n\x05\x04\x01\x02\x02\
+    \x05\x12\x03\x0c\x18\x1e\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\x0c\x1f\
+    #\n\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\x0c&'\n\x0b\n\x04\x04\x01\x08\
+    \x02\x12\x03\r\x044\n\x0c\n\x05\x04\x01\x08\x02\x01\x12\x03\r\n\x1a\n\
+    \x0b\n\x04\x04\x01\x02\x03\x12\x03\r\x1d2\n\x0c\n\x05\x04\x01\x02\x03\
+    \x05\x12\x03\r\x1d#\n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\r$-\n\x0c\n\
+    \x05\x04\x01\x02\x03\x03\x12\x03\r01\n\x0b\n\x04\x04\x01\x08\x03\x12\x03\
+    \x0e\x040\n\x0c\n\x05\x04\x01\x08\x03\x01\x12\x03\x0e\n\x19\n\x0b\n\x04\
+    \x04\x01\x02\x04\x12\x03\x0e\x1c.\n\x0c\n\x05\x04\x01\x02\x04\x05\x12\
+    \x03\x0e\x1c\x20\n\x0c\n\x05\x04\x01\x02\x04\x01\x12\x03\x0e!)\n\x0c\n\
+    \x05\x04\x01\x02\x04\x03\x12\x03\x0e,-\n\n\n\x02\x04\x02\x12\x04\x10\0\
+    \x13\x01\n\n\n\x03\x04\x02\x01\x12\x03\x10\x08\x1b\n\x0b\n\x04\x04\x02\
+    \x02\0\x12\x03\x11\x04\x17\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x11\x04\
+    \n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x11\x0b\x12\n\x0c\n\x05\x04\x02\
+    \x02\0\x03\x12\x03\x11\x15\x16\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\x12\
+    \x04\x13\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x12\x04\t\n\x0c\n\x05\
+    \x04\x02\x02\x01\x01\x12\x03\x12\n\x0e\n\x0c\n\x05\x04\x02\x02\x01\x03\
+    \x12\x03\x12\x11\x12\n\n\n\x02\x04\x03\x12\x04\x14\0\x17\x01\n\n\n\x03\
+    \x04\x03\x01\x12\x03\x14\x08\x1d\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x15\
+    \x04\x17\n\x0c\n\x05\x04\x03\x02\0\x05\x12\x03\x15\x04\n\n\x0c\n\x05\x04\
+    \x03\x02\0\x01\x12\x03\x15\x0b\x12\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\
+    \x15\x15\x16\n\x0b\n\x04\x04\x03\x02\x01\x12\x03\x16\x04\x13\n\x0c\n\x05\
+    \x04\x03\x02\x01\x05\x12\x03\x16\x04\t\n\x0c\n\x05\x04\x03\x02\x01\x01\
+    \x12\x03\x16\n\x0e\n\x0c\n\x05\x04\x03\x02\x01\x03\x12\x03\x16\x11\x12b\
+    \x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 0
rust-lib/flowy-workspace/src/protobuf/proto/errors.proto

@@ -17,6 +17,7 @@ enum ErrorCode {
     ViewThumbnailInvalid = 21;
     ViewIdInvalid = 22;
     ViewDescInvalid = 23;
+    ViewDataInvalid = 24;
     UserIdIsEmpty = 100;
     UserUnauthorized = 101;
     InternalError = 1000;

+ 2 - 1
rust-lib/flowy-workspace/src/protobuf/proto/event.proto

@@ -16,5 +16,6 @@ enum WorkspaceEvent {
     UpdateView = 203;
     DeleteView = 204;
     OpenView = 205;
-    UpdateViewData = 206;
+    SaveViewData = 206;
+    ApplyChangeset = 207;
 }

+ 1 - 1
rust-lib/flowy-workspace/src/protobuf/proto/view_create.proto

@@ -13,7 +13,7 @@ message CreateViewParams {
     string desc = 3;
     string thumbnail = 4;
     ViewType view_type = 5;
-    string data = 6;
+    bytes data = 6;
 }
 message View {
     string id = 1;

+ 6 - 2
rust-lib/flowy-workspace/src/protobuf/proto/view_update.proto

@@ -14,7 +14,11 @@ message UpdateViewParams {
     oneof one_of_thumbnail { string thumbnail = 4; };
     oneof one_of_is_trash { bool is_trash = 5; };
 }
-message UpdateViewDataRequest {
+message SaveViewDataRequest {
     string view_id = 1;
-    string data = 2;
+    bytes data = 2;
+}
+message ApplyChangesetRequest {
+    string view_id = 1;
+    bytes data = 2;
 }

+ 9 - 5
rust-lib/flowy-workspace/src/services/view_controller.rs

@@ -14,7 +14,7 @@ use crate::{
 };
 use flowy_database::SqliteConnection;
 use flowy_document::{
-    entities::doc::{CreateDocParams, Doc, QueryDocParams, UpdateDocParams},
+    entities::doc::{ApplyChangesetParams, CreateDocParams, Doc, QueryDocParams, SaveDocParams},
     module::FlowyDocument,
 };
 use std::sync::Arc;
@@ -50,7 +50,7 @@ impl ViewController {
         // TODO: rollback anything created before if failed?
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
             let _ = self.save_view(view.clone(), conn)?;
-            self.document.create(CreateDocParams::new(&view.id, &params.data), conn)?;
+            self.document.create(CreateDocParams::new(&view.id, params.data), conn)?;
 
             let repeated_view = self.read_local_views_belong_to(&view.belong_to_id, conn)?;
             notify(&view.belong_to_id, WorkspaceObservable::AppCreateView)
@@ -124,9 +124,13 @@ impl ViewController {
         Ok(())
     }
 
-    pub(crate) async fn update_view_data(&self, params: UpdateDocParams) -> Result<(), WorkspaceError> {
-        let conn = &*self.database.db_connection()?;
-        let _ = self.document.update(params, &*conn)?;
+    pub(crate) async fn update_view_data(&self, params: SaveDocParams) -> Result<(), WorkspaceError> {
+        let _ = self.document.update(params, self.database.db_pool()?).await?;
+        Ok(())
+    }
+
+    pub(crate) async fn apply_changeset(&self, params: ApplyChangesetParams) -> Result<(), WorkspaceError> {
+        let _ = self.document.apply_changeset(params).await?;
         Ok(())
     }
 }

+ 2 - 2
rust-lib/flowy-workspace/tests/workspace/helper.rs

@@ -212,9 +212,9 @@ pub fn open_view(sdk: &FlowyTestSDK, request: OpenViewRequest) -> Doc {
         .parse::<Doc>()
 }
 
-pub fn update_view_data(sdk: &FlowyTestSDK, request: UpdateViewDataRequest) {
+pub fn update_view_data(sdk: &FlowyTestSDK, request: SaveViewDataRequest) {
     FlowyWorkspaceTest::new(sdk.clone())
-        .event(UpdateViewData)
+        .event(SaveViewData)
         .request(request)
         .sync_send();
 }

+ 2 - 2
rust-lib/flowy-workspace/tests/workspace/view_test.rs

@@ -36,7 +36,7 @@ fn view_update_doc() {
     let test = ViewTest::new();
 
     let new_data = "123";
-    let request = UpdateViewDataRequest {
+    let request = SaveViewDataRequest {
         view_id: test.view.id.clone(),
         data: new_data.to_string(),
     };
@@ -54,7 +54,7 @@ fn view_update_doc() {
 fn view_update_big_doc() {
     let test = ViewTest::new();
     let new_data = "flutter ❤️ rust".repeat(1000000);
-    let request = UpdateViewDataRequest {
+    let request = SaveViewDataRequest {
         view_id: test.view.id.clone(),
         data: new_data.to_string(),
     };