Ver Fonte

remove notify from edit context

appflowy há 3 anos atrás
pai
commit
77313ab431
51 ficheiros alterados com 1109 adições e 704 exclusões
  1. 9 3
      app_flowy/lib/workspace/application/doc/doc_bloc.dart
  2. 2 2
      app_flowy/lib/workspace/domain/i_doc.dart
  3. 4 3
      app_flowy/lib/workspace/infrastructure/i_doc_impl.dart
  4. 1 2
      app_flowy/lib/workspace/infrastructure/repos/doc_repo.dart
  5. 16 16
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/doc.pb.dart
  6. 8 8
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/doc.pbjson.dart
  7. 18 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pb.dart
  8. 19 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbenum.dart
  9. 13 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbjson.dart
  10. 4 4
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pb.dart
  11. 2 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pbjson.dart
  12. 13 13
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_update.pb.dart
  13. 5 5
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_update.pbjson.dart
  14. 2 1
      backend/migrations/20210909115140_doc.sql
  15. 1 1
      backend/rustfmt.toml
  16. 1 1
      backend/src/entities/doc.rs
  17. 21 28
      backend/src/service/doc/edit_doc_context.rs
  18. 2 2
      backend/src/service/doc/sql_builder.rs
  19. 7 11
      backend/src/service/user/user_default.rs
  20. 2 1
      rust-lib/flowy-database/migrations/2021-07-22-234458_flowy-editor/up.sql
  21. 3 2
      rust-lib/flowy-database/migrations/2021-09-22-074638_flowy-doc-op/up.sql
  22. 18 16
      rust-lib/flowy-database/src/macros.rs
  23. 4 3
      rust-lib/flowy-database/src/schema.rs
  24. 2 1
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  25. 10 5
      rust-lib/flowy-document/src/entities/doc/doc.rs
  26. 16 2
      rust-lib/flowy-document/src/entities/doc/revision.rs
  27. 75 75
      rust-lib/flowy-document/src/protobuf/model/doc.rs
  28. 108 17
      rust-lib/flowy-document/src/protobuf/model/revision.rs
  29. 4 4
      rust-lib/flowy-document/src/protobuf/proto/doc.proto
  30. 5 0
      rust-lib/flowy-document/src/protobuf/proto/revision.proto
  31. 7 0
      rust-lib/flowy-document/src/services/cache.rs
  32. 43 10
      rust-lib/flowy-document/src/services/doc/doc_controller.rs
  33. 30 37
      rust-lib/flowy-document/src/services/doc/document/document.rs
  34. 21 36
      rust-lib/flowy-document/src/services/doc/edit_doc_context.rs
  35. 70 39
      rust-lib/flowy-document/src/services/doc/rev_manager.rs
  36. 9 9
      rust-lib/flowy-document/src/sql_tables/doc/doc_op_sql.rs
  37. 59 23
      rust-lib/flowy-document/src/sql_tables/doc/doc_op_table.rs
  38. 2 2
      rust-lib/flowy-document/src/sql_tables/doc/doc_table.rs
  39. 98 86
      rust-lib/flowy-document/tests/editor/attribute_test.rs
  40. 78 15
      rust-lib/flowy-document/tests/editor/mod.rs
  41. 96 39
      rust-lib/flowy-document/tests/editor/op_test.rs
  42. 50 47
      rust-lib/flowy-document/tests/editor/undo_redo_test.rs
  43. 9 8
      rust-lib/flowy-ot/src/core/delta/delta.rs
  44. 17 6
      rust-lib/flowy-workspace/src/entities/view/view_create.rs
  45. 34 26
      rust-lib/flowy-workspace/src/entities/view/view_update.rs
  46. 3 4
      rust-lib/flowy-workspace/src/handlers/view_handler.rs
  47. 15 15
      rust-lib/flowy-workspace/src/protobuf/model/view_create.rs
  48. 70 70
      rust-lib/flowy-workspace/src/protobuf/model/view_update.rs
  49. 1 1
      rust-lib/flowy-workspace/src/protobuf/proto/view_create.proto
  50. 1 1
      rust-lib/flowy-workspace/src/protobuf/proto/view_update.proto
  51. 1 1
      rust-lib/rustfmt.toml

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

@@ -30,8 +30,8 @@ class DocBloc extends Bloc<DocEvent, DocState> {
       (doc) {
         final flowyDoc = FlowyDoc(
             doc: doc,
-            data: _decodeToDocument(
-              Uint8List.fromList(doc.data),
+            data: _decodeJsonToDocument(
+              doc.data,
             ),
             iDocImpl: iDocImpl);
         return DocState.loadDoc(flowyDoc);
@@ -42,11 +42,17 @@ class DocBloc extends Bloc<DocEvent, DocState> {
     );
   }
 
-  Document _decodeToDocument(Uint8List data) {
+  Document _decodeListToDocument(Uint8List data) {
     final json = jsonDecode(utf8.decode(data));
     final document = Document.fromJson(json);
     return document;
   }
+
+  Document _decodeJsonToDocument(String data) {
+    final json = jsonDecode(data);
+    final document = Document.fromJson(json);
+    return document;
+  }
 }
 
 @freezed

+ 2 - 2
app_flowy/lib/workspace/domain/i_doc.dart

@@ -26,7 +26,7 @@ class FlowyDoc implements EditorDeltaSender {
 
     result.fold((rustDoc) {
       // final json = utf8.decode(doc.data);
-      final rustDelta = Delta.fromJson(jsonDecode(utf8.decode(rustDoc.data)));
+      final rustDelta = Delta.fromJson(jsonDecode(rustDoc.data));
 
       if (delta != rustDelta) {
         Log.error("Receive : $rustDelta");
@@ -41,6 +41,6 @@ class FlowyDoc implements EditorDeltaSender {
 
 abstract class IDoc {
   Future<Either<Doc, WorkspaceError>> readDoc();
-  Future<Either<Doc, WorkspaceError>> applyChangeset({String? json});
+  Future<Either<Doc, WorkspaceError>> applyChangeset({required String json});
   Future<Either<Unit, WorkspaceError>> closeDoc();
 }

+ 4 - 3
app_flowy/lib/workspace/infrastructure/i_doc_impl.dart

@@ -24,12 +24,13 @@ class IDocImpl extends IDoc {
   }
 
   @override
-  Future<Either<Doc, WorkspaceError>> applyChangeset({String? json}) {
-    return repo.applyChangeset(data: _encodeText(json));
+  Future<Either<Doc, WorkspaceError>> applyChangeset({required String json}) {
+    return repo.applyDelta(data: json);
   }
 }
 
-Uint8List _encodeText(String? json) {
+// ignore: unused_element
+Uint8List _encodeJsonText(String? json) {
   final data = utf8.encode(json ?? "");
   return Uint8List.fromList(data);
 }

+ 1 - 2
app_flowy/lib/workspace/infrastructure/repos/doc_repo.dart

@@ -18,8 +18,7 @@ class DocRepository {
     return WorkspaceEventOpenView(request).send();
   }
 
-  Future<Either<Doc, WorkspaceError>> applyChangeset(
-      {required Uint8List data}) {
+  Future<Either<Doc, WorkspaceError>> applyDelta({required String data}) {
     final request = DocDelta.create()
       ..docId = docId
       ..data = data;

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

@@ -13,14 +13,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')
-    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
     ..hasRequiredFields = false
   ;
 
   CreateDocParams._() : super();
   factory CreateDocParams({
     $core.String? id,
-    $core.List<$core.int>? data,
+    $core.String? data,
   }) {
     final _result = create();
     if (id != null) {
@@ -62,9 +62,9 @@ class CreateDocParams extends $pb.GeneratedMessage {
   void clearId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.List<$core.int> get data => $_getN(1);
+  $core.String get data => $_getSZ(1);
   @$pb.TagNumber(2)
-  set data($core.List<$core.int> v) { $_setBytes(1, v); }
+  set data($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
   $core.bool hasData() => $_has(1);
   @$pb.TagNumber(2)
@@ -74,7 +74,7 @@ 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')
-    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
     ..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revId')
     ..hasRequiredFields = false
   ;
@@ -82,7 +82,7 @@ class Doc extends $pb.GeneratedMessage {
   Doc._() : super();
   factory Doc({
     $core.String? id,
-    $core.List<$core.int>? data,
+    $core.String? data,
     $fixnum.Int64? revId,
   }) {
     final _result = create();
@@ -128,9 +128,9 @@ class Doc extends $pb.GeneratedMessage {
   void clearId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.List<$core.int> get data => $_getN(1);
+  $core.String get data => $_getSZ(1);
   @$pb.TagNumber(2)
-  set data($core.List<$core.int> v) { $_setBytes(1, v); }
+  set data($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
   $core.bool hasData() => $_has(1);
   @$pb.TagNumber(2)
@@ -149,7 +149,7 @@ class Doc extends $pb.GeneratedMessage {
 class UpdateDocParams extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UpdateDocParams', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
-    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
     ..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revId')
     ..hasRequiredFields = false
   ;
@@ -157,7 +157,7 @@ class UpdateDocParams extends $pb.GeneratedMessage {
   UpdateDocParams._() : super();
   factory UpdateDocParams({
     $core.String? docId,
-    $core.List<$core.int>? data,
+    $core.String? data,
     $fixnum.Int64? revId,
   }) {
     final _result = create();
@@ -203,9 +203,9 @@ class UpdateDocParams extends $pb.GeneratedMessage {
   void clearDocId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.List<$core.int> get data => $_getN(1);
+  $core.String get data => $_getSZ(1);
   @$pb.TagNumber(2)
-  set data($core.List<$core.int> v) { $_setBytes(1, v); }
+  set data($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
   $core.bool hasData() => $_has(1);
   @$pb.TagNumber(2)
@@ -224,14 +224,14 @@ class UpdateDocParams extends $pb.GeneratedMessage {
 class DocDelta extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocDelta', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
-    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
     ..hasRequiredFields = false
   ;
 
   DocDelta._() : super();
   factory DocDelta({
     $core.String? docId,
-    $core.List<$core.int>? data,
+    $core.String? data,
   }) {
     final _result = create();
     if (docId != null) {
@@ -273,9 +273,9 @@ class DocDelta extends $pb.GeneratedMessage {
   void clearDocId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.List<$core.int> get data => $_getN(1);
+  $core.String get data => $_getSZ(1);
   @$pb.TagNumber(2)
-  set data($core.List<$core.int> v) { $_setBytes(1, v); }
+  set data($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
   $core.bool hasData() => $_has(1);
   @$pb.TagNumber(2)

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

@@ -13,47 +13,47 @@ 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': 12, '10': 'data'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 9, '10': 'data'},
   ],
 };
 
 /// Descriptor for `CreateDocParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List createDocParamsDescriptor = $convert.base64Decode('Cg9DcmVhdGVEb2NQYXJhbXMSDgoCaWQYASABKAlSAmlkEhIKBGRhdGEYAiABKAxSBGRhdGE=');
+final $typed_data.Uint8List createDocParamsDescriptor = $convert.base64Decode('Cg9DcmVhdGVEb2NQYXJhbXMSDgoCaWQYASABKAlSAmlkEhIKBGRhdGEYAiABKAlSBGRhdGE=');
 @$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': 12, '10': 'data'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 9, '10': 'data'},
     const {'1': 'rev_id', '3': 3, '4': 1, '5': 3, '10': 'revId'},
   ],
 };
 
 /// Descriptor for `Doc`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List docDescriptor = $convert.base64Decode('CgNEb2MSDgoCaWQYASABKAlSAmlkEhIKBGRhdGEYAiABKAxSBGRhdGESFQoGcmV2X2lkGAMgASgDUgVyZXZJZA==');
+final $typed_data.Uint8List docDescriptor = $convert.base64Decode('CgNEb2MSDgoCaWQYASABKAlSAmlkEhIKBGRhdGEYAiABKAlSBGRhdGESFQoGcmV2X2lkGAMgASgDUgVyZXZJZA==');
 @$core.Deprecated('Use updateDocParamsDescriptor instead')
 const UpdateDocParams$json = const {
   '1': 'UpdateDocParams',
   '2': const [
     const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
-    const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 9, '10': 'data'},
     const {'1': 'rev_id', '3': 3, '4': 1, '5': 3, '10': 'revId'},
   ],
 };
 
 /// Descriptor for `UpdateDocParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List updateDocParamsDescriptor = $convert.base64Decode('Cg9VcGRhdGVEb2NQYXJhbXMSFQoGZG9jX2lkGAEgASgJUgVkb2NJZBISCgRkYXRhGAIgASgMUgRkYXRhEhUKBnJldl9pZBgDIAEoA1IFcmV2SWQ=');
+final $typed_data.Uint8List updateDocParamsDescriptor = $convert.base64Decode('Cg9VcGRhdGVEb2NQYXJhbXMSFQoGZG9jX2lkGAEgASgJUgVkb2NJZBISCgRkYXRhGAIgASgJUgRkYXRhEhUKBnJldl9pZBgDIAEoA1IFcmV2SWQ=');
 @$core.Deprecated('Use docDeltaDescriptor instead')
 const DocDelta$json = const {
   '1': 'DocDelta',
   '2': const [
     const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
-    const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'},
+    const {'1': 'data', '3': 2, '4': 1, '5': 9, '10': 'data'},
   ],
 };
 
 /// Descriptor for `DocDelta`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List docDeltaDescriptor = $convert.base64Decode('CghEb2NEZWx0YRIVCgZkb2NfaWQYASABKAlSBWRvY0lkEhIKBGRhdGEYAiABKAxSBGRhdGE=');
+final $typed_data.Uint8List docDeltaDescriptor = $convert.base64Decode('CghEb2NEZWx0YRIVCgZkb2NfaWQYASABKAlSBWRvY0lkEhIKBGRhdGEYAiABKAlSBGRhdGE=');
 @$core.Deprecated('Use queryDocParamsDescriptor instead')
 const QueryDocParams$json = const {
   '1': 'QueryDocParams',

+ 18 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pb.dart

@@ -10,6 +10,10 @@ import 'dart:core' as $core;
 import 'package:fixnum/fixnum.dart' as $fixnum;
 import 'package:protobuf/protobuf.dart' as $pb;
 
+import 'revision.pbenum.dart';
+
+export 'revision.pbenum.dart';
+
 class Revision extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Revision', createEmptyInstance: create)
     ..aInt64(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'baseRevId')
@@ -17,6 +21,7 @@ class Revision extends $pb.GeneratedMessage {
     ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'delta', $pb.PbFieldType.OY)
     ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'md5')
     ..aOS(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
+    ..e<RevType>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'ty', $pb.PbFieldType.OE, defaultOrMaker: RevType.Local, valueOf: RevType.valueOf, enumValues: RevType.values)
     ..hasRequiredFields = false
   ;
 
@@ -27,6 +32,7 @@ class Revision extends $pb.GeneratedMessage {
     $core.List<$core.int>? delta,
     $core.String? md5,
     $core.String? docId,
+    RevType? ty,
   }) {
     final _result = create();
     if (baseRevId != null) {
@@ -44,6 +50,9 @@ class Revision extends $pb.GeneratedMessage {
     if (docId != null) {
       _result.docId = docId;
     }
+    if (ty != null) {
+      _result.ty = ty;
+    }
     return _result;
   }
   factory Revision.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -111,5 +120,14 @@ class Revision extends $pb.GeneratedMessage {
   $core.bool hasDocId() => $_has(4);
   @$pb.TagNumber(5)
   void clearDocId() => clearField(5);
+
+  @$pb.TagNumber(6)
+  RevType get ty => $_getN(5);
+  @$pb.TagNumber(6)
+  set ty(RevType v) { setField(6, v); }
+  @$pb.TagNumber(6)
+  $core.bool hasTy() => $_has(5);
+  @$pb.TagNumber(6)
+  void clearTy() => clearField(6);
 }
 

+ 19 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbenum.dart

@@ -5,3 +5,22 @@
 // @dart = 2.12
 // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
 
+// ignore_for_file: UNDEFINED_SHOWN_NAME
+import 'dart:core' as $core;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class RevType extends $pb.ProtobufEnum {
+  static const RevType Local = RevType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Local');
+  static const RevType Remote = RevType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Remote');
+
+  static const $core.List<RevType> values = <RevType> [
+    Local,
+    Remote,
+  ];
+
+  static final $core.Map<$core.int, RevType> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static RevType? valueOf($core.int value) => _byValue[value];
+
+  const RevType._($core.int v, $core.String n) : super(v, n);
+}
+

+ 13 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbjson.dart

@@ -8,6 +8,17 @@
 import 'dart:core' as $core;
 import 'dart:convert' as $convert;
 import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use revTypeDescriptor instead')
+const RevType$json = const {
+  '1': 'RevType',
+  '2': const [
+    const {'1': 'Local', '2': 0},
+    const {'1': 'Remote', '2': 1},
+  ],
+};
+
+/// Descriptor for `RevType`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List revTypeDescriptor = $convert.base64Decode('CgdSZXZUeXBlEgkKBUxvY2FsEAASCgoGUmVtb3RlEAE=');
 @$core.Deprecated('Use revisionDescriptor instead')
 const Revision$json = const {
   '1': 'Revision',
@@ -17,8 +28,9 @@ const Revision$json = const {
     const {'1': 'delta', '3': 3, '4': 1, '5': 12, '10': 'delta'},
     const {'1': 'md5', '3': 4, '4': 1, '5': 9, '10': 'md5'},
     const {'1': 'doc_id', '3': 5, '4': 1, '5': 9, '10': 'docId'},
+    const {'1': 'ty', '3': 6, '4': 1, '5': 14, '6': '.RevType', '10': 'ty'},
   ],
 };
 
 /// Descriptor for `Revision`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List revisionDescriptor = $convert.base64Decode('CghSZXZpc2lvbhIeCgtiYXNlX3Jldl9pZBgBIAEoA1IJYmFzZVJldklkEhUKBnJldl9pZBgCIAEoA1IFcmV2SWQSFAoFZGVsdGEYAyABKAxSBWRlbHRhEhAKA21kNRgEIAEoCVIDbWQ1EhUKBmRvY19pZBgFIAEoCVIFZG9jSWQ=');
+final $typed_data.Uint8List revisionDescriptor = $convert.base64Decode('CghSZXZpc2lvbhIeCgtiYXNlX3Jldl9pZBgBIAEoA1IJYmFzZVJldklkEhUKBnJldl9pZBgCIAEoA1IFcmV2SWQSFAoFZGVsdGEYAyABKAxSBWRlbHRhEhAKA21kNRgEIAEoCVIDbWQ1EhUKBmRvY19pZBgFIAEoCVIFZG9jSWQSGAoCdHkYBiABKA4yCC5SZXZUeXBlUgJ0eQ==');

+ 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)
-    ..a<$core.List<$core.int>>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY)
+    ..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
     ..hasRequiredFields = false
   ;
 
@@ -148,7 +148,7 @@ class CreateViewParams extends $pb.GeneratedMessage {
     $core.String? desc,
     $core.String? thumbnail,
     ViewType? viewType,
-    $core.List<$core.int>? data,
+    $core.String? data,
   }) {
     final _result = create();
     if (belongToId != null) {
@@ -238,9 +238,9 @@ class CreateViewParams extends $pb.GeneratedMessage {
   void clearViewType() => clearField(5);
 
   @$pb.TagNumber(6)
-  $core.List<$core.int> get data => $_getN(5);
+  $core.String get data => $_getSZ(5);
   @$pb.TagNumber(6)
-  set data($core.List<$core.int> v) { $_setBytes(5, v); }
+  set data($core.String v) { $_setString(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': 12, '10': 'data'},
+    const {'1': 'data', '3': 6, '4': 1, '5': 9, '10': 'data'},
   ],
 };
 
 /// Descriptor for `CreateViewParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlEhIKBGRhdGEYBiABKAxSBGRhdGE=');
+final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlEhIKBGRhdGEYBiABKAlSBGRhdGE=');
 @$core.Deprecated('Use viewDescriptor instead')
 const View$json = const {
   '1': 'View',

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

@@ -319,15 +319,15 @@ class UpdateViewParams extends $pb.GeneratedMessage {
   void clearIsTrash() => clearField(5);
 }
 
-class ApplyChangesetRequest extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ApplyChangesetRequest', createEmptyInstance: create)
+class DocDeltaRequest extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocDeltaRequest', 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({
+  DocDeltaRequest._() : super();
+  factory DocDeltaRequest({
     $core.String? viewId,
     $core.List<$core.int>? data,
   }) {
@@ -340,26 +340,26 @@ class ApplyChangesetRequest extends $pb.GeneratedMessage {
     }
     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);
+  factory DocDeltaRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory DocDeltaRequest.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);
+  DocDeltaRequest clone() => DocDeltaRequest()..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
+  DocDeltaRequest copyWith(void Function(DocDeltaRequest) updates) => super.copyWith((message) => updates(message as DocDeltaRequest)) as DocDeltaRequest; // 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>();
+  static DocDeltaRequest create() => DocDeltaRequest._();
+  DocDeltaRequest createEmptyInstance() => create();
+  static $pb.PbList<DocDeltaRequest> createRepeated() => $pb.PbList<DocDeltaRequest>();
   @$core.pragma('dart2js:noInline')
-  static ApplyChangesetRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplyChangesetRequest>(create);
-  static ApplyChangesetRequest? _defaultInstance;
+  static DocDeltaRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocDeltaRequest>(create);
+  static DocDeltaRequest? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get viewId => $_getSZ(0);

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

@@ -48,14 +48,14 @@ const UpdateViewParams$json = const {
 
 /// Descriptor for `UpdateViewParams`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List updateViewParamsDescriptor = $convert.base64Decode('ChBVcGRhdGVWaWV3UGFyYW1zEhcKB3ZpZXdfaWQYASABKAlSBnZpZXdJZBIUCgRuYW1lGAIgASgJSABSBG5hbWUSFAoEZGVzYxgDIAEoCUgBUgRkZXNjEh4KCXRodW1ibmFpbBgEIAEoCUgCUgl0aHVtYm5haWwSGwoIaXNfdHJhc2gYBSABKAhIA1IHaXNUcmFzaEINCgtvbmVfb2ZfbmFtZUINCgtvbmVfb2ZfZGVzY0ISChBvbmVfb2ZfdGh1bWJuYWlsQhEKD29uZV9vZl9pc190cmFzaA==');
-@$core.Deprecated('Use applyChangesetRequestDescriptor instead')
-const ApplyChangesetRequest$json = const {
-  '1': 'ApplyChangesetRequest',
+@$core.Deprecated('Use docDeltaRequestDescriptor instead')
+const DocDeltaRequest$json = const {
+  '1': 'DocDeltaRequest',
   '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=');
+/// Descriptor for `DocDeltaRequest`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List docDeltaRequestDescriptor = $convert.base64Decode('Cg9Eb2NEZWx0YVJlcXVlc3QSFwoHdmlld19pZBgBIAEoCVIGdmlld0lkEhIKBGRhdGEYAiABKAxSBGRhdGE=');

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

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

+ 1 - 1
backend/rustfmt.toml

@@ -1,5 +1,5 @@
 # https://rust-lang.github.io/rustfmt/?version=master&search=
-max_width = 100
+max_width = 120
 tab_spaces = 4
 fn_single_line = true
 match_block_trailing_comma = true

+ 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: Vec<u8>,
+    pub(crate) data: String,
     pub(crate) rev_id: i64,
 }
 

+ 21 - 28
backend/src/service/doc/edit_doc_context.rs

@@ -4,11 +4,11 @@ use crate::service::{
     ws::{entities::Socket, WsMessageAdaptor},
 };
 use actix_web::web::Data;
-use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
+use byteorder::{BigEndian, WriteBytesExt};
 use bytes::Bytes;
 use flowy_document::{
     entities::ws::{WsDataType, WsDocumentData},
-    protobuf::{Doc, Revision, UpdateDocParams},
+    protobuf::{Doc, RevType, Revision, UpdateDocParams},
     services::doc::Document,
 };
 use flowy_net::errors::{internal_error, ServerError};
@@ -31,7 +31,7 @@ pub(crate) struct EditDocContext {
 
 impl EditDocContext {
     pub(crate) fn new(doc: Doc, pg_pool: Data<PgPool>) -> Result<Self, ServerError> {
-        let delta = Delta::from_bytes(doc.data).map_err(internal_error)?;
+        let delta = Delta::from_bytes(&doc.data).map_err(internal_error)?;
         let document = Arc::new(RwLock::new(Document::from_delta(delta)));
         Ok(Self {
             doc_id: doc.id.clone(),
@@ -42,29 +42,25 @@ impl EditDocContext {
     }
 
     #[tracing::instrument(level = "debug", skip(self, socket, revision))]
-    pub(crate) async fn apply_revision(
-        &self,
-        socket: Socket,
-        revision: Revision,
-    ) -> Result<(), ServerError> {
+    pub(crate) async fn apply_revision(&self, socket: Socket, revision: Revision) -> Result<(), ServerError> {
         let _ = self.verify_md5(&revision)?;
 
         if self.rev_id > revision.rev_id {
-            let (cli_prime, server_prime) = self.compose(revision.delta).map_err(internal_error)?;
+            let (cli_prime, server_prime) = self.compose(&revision.delta).map_err(internal_error)?;
             let _ = self.update_document_delta(server_prime)?;
 
             log::debug!("{} client delta: {}", self.doc_id, cli_prime.to_json());
             let cli_revision = self.mk_revision(revision.rev_id, cli_prime);
-            let ws_cli_revision = mk_ws_rev_message(&self.doc_id, cli_revision);
+            let ws_cli_revision = mk_rev_ws_message(&self.doc_id, cli_revision);
             socket.do_send(ws_cli_revision).map_err(internal_error)?;
             Ok(())
         } else {
-            let delta = Delta::from_bytes(revision.delta.clone()).map_err(internal_error)?;
+            let delta = Delta::from_bytes(&revision.delta).map_err(internal_error)?;
             let _ = self.update_document_delta(delta)?;
-            socket.do_send(mk_ws_acked_message(&revision));
+            socket.do_send(mk_acked_ws_message(&revision));
 
             // Opti: save with multiple revisions
-            let _ = self.save_doc_to_disk(&revision).await?;
+            let _ = self.save_revision(&revision).await?;
             Ok(())
         }
     }
@@ -78,23 +74,20 @@ impl EditDocContext {
             delta: delta_data,
             md5,
             doc_id: self.doc_id.to_string(),
+            ty: RevType::Remote,
             ..Default::default()
         };
         revision
     }
 
     #[tracing::instrument(level = "debug", skip(self, delta_data))]
-    fn compose(&self, delta_data: Vec<u8>) -> Result<(Delta, Delta), OTError> {
-        log::debug!(
-            "{} document data: {}",
-            self.doc_id,
-            self.document.read().to_json()
-        );
+    fn compose(&self, delta_data: &Vec<u8>) -> Result<(Delta, Delta), OTError> {
+        log::debug!("{} document data: {}", self.doc_id, self.document.read().to_json());
         let doc_delta = self.document.read().delta().clone();
         let cli_delta = Delta::from_bytes(delta_data)?;
-        let (a, b) = doc_delta.transform(&cli_delta)?;
+        let (cli_prime, server_prime) = doc_delta.transform(&cli_delta)?;
 
-        Ok((a, b))
+        Ok((cli_prime, server_prime))
     }
 
     #[tracing::instrument(level = "debug", skip(self, delta))]
@@ -105,9 +98,7 @@ impl EditDocContext {
                 log::error!("Failed to acquire write lock of document");
             },
             Some(mut write_guard) => {
-                let _ = write_guard
-                    .apply_delta(delta.clone())
-                    .map_err(internal_error)?;
+                let _ = write_guard.compose_delta(&delta).map_err(internal_error)?;
 
                 log::debug!("Document: {}", write_guard.to_plain_string());
             },
@@ -122,18 +113,20 @@ impl EditDocContext {
         Ok(())
     }
 
-    async fn save_doc_to_disk(&self, revision: &Revision) -> Result<(), ServerError> {
+    #[tracing::instrument(level = "debug", skip(self, revision))]
+    async fn save_revision(&self, revision: &Revision) -> Result<(), ServerError> {
         let mut params = UpdateDocParams::new();
         params.set_doc_id(self.doc_id.clone());
-        params.set_data(self.document.read().to_bytes());
+        params.set_data(self.document.read().to_json());
         params.set_rev_id(revision.rev_id);
+
         let _ = update_doc(self.pg_pool.get_ref(), params).await?;
 
         Ok(())
     }
 }
 
-fn mk_ws_rev_message(doc_id: &str, revision: Revision) -> WsMessageAdaptor {
+fn mk_rev_ws_message(doc_id: &str, revision: Revision) -> WsMessageAdaptor {
     let bytes = revision.write_to_bytes().unwrap();
 
     let data = WsDocumentData {
@@ -147,7 +140,7 @@ fn mk_ws_rev_message(doc_id: &str, revision: Revision) -> WsMessageAdaptor {
     WsMessageAdaptor(bytes)
 }
 
-fn mk_ws_acked_message(revision: &Revision) -> WsMessageAdaptor {
+fn mk_acked_ws_message(revision: &Revision) -> WsMessageAdaptor {
     let mut wtr = vec![];
     let _ = wtr.write_i64::<BigEndian>(revision.rev_id);
 

+ 2 - 2
backend/src/service/doc/sql_builder.rs

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

+ 7 - 11
backend/src/service/user/user_default.rs

@@ -1,9 +1,3 @@
-use flowy_net::errors::ServerError;
-use flowy_workspace::{
-    entities::view::default_delta,
-    protobuf::{App, CreateViewParams, View, ViewType, Workspace},
-};
-
 use crate::{
     service::{
         app::sql_builder::NewAppSqlBuilder as AppBuilder,
@@ -12,6 +6,11 @@ use crate::{
     },
     sqlx_ext::{map_sqlx_error, DBTransaction},
 };
+use flowy_net::errors::ServerError;
+use flowy_workspace::{
+    entities::view::VIEW_DEFAULT_DATA,
+    protobuf::{App, CreateViewParams, View, ViewType, Workspace},
+};
 
 pub async fn create_default_workspace(
     transaction: &mut DBTransaction<'_>,
@@ -24,10 +23,7 @@ pub async fn create_default_workspace(
     Ok(workspace)
 }
 
-async fn create_workspace(
-    transaction: &mut DBTransaction<'_>,
-    user_id: &str,
-) -> Result<Workspace, ServerError> {
+async fn create_workspace(transaction: &mut DBTransaction<'_>, user_id: &str) -> Result<Workspace, ServerError> {
     let (sql, args, workspace) = WorkspaceBuilder::new(user_id.as_ref())
         .name("DefaultWorkspace")
         .desc("Workspace created by AppFlowy")
@@ -66,7 +62,7 @@ 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: default_delta(),
+        data: VIEW_DEFAULT_DATA.to_string(),
         unknown_fields: Default::default(),
         cached_size: Default::default(),
     };

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

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

+ 3 - 2
rust-lib/flowy-database/migrations/2021-09-22-074638_flowy-doc-op/up.sql

@@ -1,9 +1,10 @@
 -- Your SQL goes here
-CREATE TABLE op_table (
+CREATE TABLE rev_table (
     doc_id TEXT NOT NULL PRIMARY KEY,
     base_rev_id BIGINT NOT NULL DEFAULT 0,
     rev_id BIGINT NOT NULL DEFAULT 0,
     data BLOB NOT NULL DEFAULT (x''),
     md5 TEXT NOT NULL DEFAULT '',
-    state INTEGER NOT NULL DEFAULT 0
+    state INTEGER NOT NULL DEFAULT 0,
+    ty INTEGER NOT NULL DEFAULT 0
 );

+ 18 - 16
rust-lib/flowy-database/src/macros.rs

@@ -112,13 +112,18 @@ macro_rules! impl_sql_binary_expression {
             *const [u8]: diesel::deserialize::FromSql<diesel::sql_types::Binary, DB>,
         {
             fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> {
-                let slice_ptr = <*const [u8] as diesel::deserialize::FromSql<diesel::sql_types::Binary, DB>>::from_sql(bytes)?;
+                let slice_ptr =
+                    <*const [u8] as diesel::deserialize::FromSql<diesel::sql_types::Binary, DB>>::from_sql(bytes)?;
                 let bytes = unsafe { &*slice_ptr };
 
                 match $target::try_from(bytes) {
                     Ok(object) => Ok(object),
                     Err(e) => {
-                        log::error!("{:?} deserialize from bytes fail. {:?}", std::any::type_name::<$target>(), e);
+                        log::error!(
+                            "{:?} deserialize from bytes fail. {:?}",
+                            std::any::type_name::<$target>(),
+                            e
+                        );
                         panic!();
                     },
                 }
@@ -130,28 +135,25 @@ macro_rules! impl_sql_binary_expression {
 #[macro_export]
 macro_rules! impl_sql_integer_expression {
     ($target:ident) => {
-        use diesel::{
-            deserialize,
-            deserialize::FromSql,
-            serialize,
-            serialize::{Output, ToSql},
-        };
-        use std::io::Write;
-
-        impl<DB> ToSql<Integer, DB> for $target
+        impl<DB> diesel::serialize::ToSql<Integer, DB> for $target
         where
             DB: diesel::backend::Backend,
-            i32: ToSql<Integer, DB>,
+            i32: diesel::serialize::ToSql<Integer, DB>,
         {
-            fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> serialize::Result { (*self as i32).to_sql(out) }
+            fn to_sql<W: std::io::Write>(
+                &self,
+                out: &mut diesel::serialize::Output<W, DB>,
+            ) -> diesel::serialize::Result {
+                (*self as i32).to_sql(out)
+            }
         }
 
-        impl<DB> FromSql<Integer, DB> for $target
+        impl<DB> diesel::deserialize::FromSql<Integer, DB> for $target
         where
             DB: diesel::backend::Backend,
-            i32: FromSql<Integer, DB>,
+            i32: diesel::deserialize::FromSql<Integer, DB>,
         {
-            fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
+            fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> {
                 let smaill_int = i32::from_sql(bytes)?;
                 Ok($target::from(smaill_int))
             }

+ 4 - 3
rust-lib/flowy-database/src/schema.rs

@@ -16,19 +16,20 @@ table! {
 table! {
     doc_table (id) {
         id -> Text,
-        data -> Binary,
+        data -> Text,
         revision -> BigInt,
     }
 }
 
 table! {
-    op_table (doc_id) {
+    rev_table (doc_id) {
         doc_id -> Text,
         base_rev_id -> BigInt,
         rev_id -> BigInt,
         data -> Binary,
         md5 -> Text,
         state -> Integer,
+        ty -> Integer,
     }
 }
 
@@ -72,7 +73,7 @@ table! {
 allow_tables_to_appear_in_same_query!(
     app_table,
     doc_table,
-    op_table,
+    rev_table,
     user_table,
     view_table,
     workspace_table,

+ 2 - 1
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -41,7 +41,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "CurrentWorkspace"
         | "UpdateViewRequest"
         | "UpdateViewParams"
-        | "ApplyChangesetRequest"
+        | "DocDeltaRequest"
         | "DeleteViewRequest"
         | "DeleteViewParams"
         | "QueryViewRequest"
@@ -81,6 +81,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "ErrorCode"
         | "WorkspaceObservable"
         | "WsModule"
+        | "RevType"
         | "WsDataType"
         | "DocObservable"
         | "FFIStatusCode"

+ 10 - 5
rust-lib/flowy-document/src/entities/doc/doc.rs

@@ -6,11 +6,16 @@ pub struct CreateDocParams {
     pub id: String,
 
     #[pb(index = 2)]
-    pub data: Vec<u8>,
+    pub data: String,
 }
 
 impl CreateDocParams {
-    pub fn new(id: &str, data: Vec<u8>) -> Self { Self { id: id.to_owned(), data } }
+    pub fn new(id: &str, data: String) -> Self {
+        Self {
+            id: id.to_owned(),
+            data,
+        }
+    }
 }
 
 #[derive(ProtoBuf, Default, Debug, Clone, Eq, PartialEq)]
@@ -19,7 +24,7 @@ pub struct Doc {
     pub id: String,
 
     #[pb(index = 2)]
-    pub data: Vec<u8>,
+    pub data: String,
 
     #[pb(index = 3)]
     pub rev_id: i64,
@@ -31,7 +36,7 @@ pub struct UpdateDocParams {
     pub doc_id: String,
 
     #[pb(index = 2)]
-    pub data: Vec<u8>,
+    pub data: String,
 
     #[pb(index = 3)]
     pub rev_id: i64,
@@ -43,7 +48,7 @@ pub struct DocDelta {
     pub doc_id: String,
 
     #[pb(index = 2)]
-    pub data: Vec<u8>, // Delta
+    pub data: String, // Delta
 }
 
 #[derive(ProtoBuf, Default, Debug, Clone)]

+ 16 - 2
rust-lib/flowy-document/src/entities/doc/revision.rs

@@ -1,4 +1,14 @@
-use flowy_derive::ProtoBuf;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+
+#[derive(Debug, ProtoBuf_Enum, Clone, Eq, PartialEq)]
+pub enum RevType {
+    Local  = 0,
+    Remote = 1,
+}
+
+impl std::default::Default for RevType {
+    fn default() -> Self { RevType::Local }
+}
 
 #[derive(Debug, Clone, Default, ProtoBuf)]
 pub struct Revision {
@@ -16,16 +26,20 @@ pub struct Revision {
 
     #[pb(index = 5)]
     pub doc_id: String,
+
+    #[pb(index = 6)]
+    pub ty: RevType,
 }
 
 impl Revision {
-    pub fn new(base_rev_id: i64, rev_id: i64, delta: Vec<u8>, md5: String, doc_id: String) -> Revision {
+    pub fn new(base_rev_id: i64, rev_id: i64, delta: Vec<u8>, md5: String, doc_id: String, ty: RevType) -> Revision {
         Self {
             base_rev_id,
             rev_id,
             delta,
             md5,
             doc_id,
+            ty,
         }
     }
 }

+ 75 - 75
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::vec::Vec<u8>,
+    pub data: ::std::string::String,
     // 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())
     }
 
-    // bytes data = 2;
+    // string data = 2;
 
 
-    pub fn get_data(&self) -> &[u8] {
+    pub fn get_data(&self) -> &str {
         &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::vec::Vec<u8>) {
+    pub fn set_data(&mut self, v: ::std::string::String) {
         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> {
+    pub fn mut_data(&mut self) -> &mut ::std::string::String {
         &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())
+    pub fn take_data(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.data, ::std::string::String::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_bytes_into(wire_type, is, &mut self.data)?;
+                    ::protobuf::rt::read_singular_proto3_string_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::bytes_size(2, &self.data);
+            my_size += ::protobuf::rt::string_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_bytes(2, &self.data)?;
+            os.write_string(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::ProtobufTypeBytes>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "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::vec::Vec<u8>,
+    pub data: ::std::string::String,
     pub rev_id: i64,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
@@ -272,10 +272,10 @@ impl Doc {
         ::std::mem::replace(&mut self.id, ::std::string::String::new())
     }
 
-    // bytes data = 2;
+    // string data = 2;
 
 
-    pub fn get_data(&self) -> &[u8] {
+    pub fn get_data(&self) -> &str {
         &self.data
     }
     pub fn clear_data(&mut self) {
@@ -283,19 +283,19 @@ impl Doc {
     }
 
     // Param is passed by value, moved
-    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
+    pub fn set_data(&mut self, v: ::std::string::String) {
         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> {
+    pub fn mut_data(&mut self) -> &mut ::std::string::String {
         &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())
+    pub fn take_data(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.data, ::std::string::String::new())
     }
 
     // int64 rev_id = 3;
@@ -327,7 +327,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_bytes_into(wire_type, is, &mut self.data)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
                 },
                 3 => {
                     if wire_type != ::protobuf::wire_format::WireTypeVarint {
@@ -352,7 +352,7 @@ impl ::protobuf::Message for Doc {
             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::string_size(2, &self.data);
         }
         if self.rev_id != 0 {
             my_size += ::protobuf::rt::value_size(3, self.rev_id, ::protobuf::wire_format::WireTypeVarint);
@@ -367,7 +367,7 @@ impl ::protobuf::Message for Doc {
             os.write_string(1, &self.id)?;
         }
         if !self.data.is_empty() {
-            os.write_bytes(2, &self.data)?;
+            os.write_string(2, &self.data)?;
         }
         if self.rev_id != 0 {
             os.write_int64(3, self.rev_id)?;
@@ -415,7 +415,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::ProtobufTypeBytes>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "data",
                 |m: &Doc| { &m.data },
                 |m: &mut Doc| { &mut m.data },
@@ -464,7 +464,7 @@ impl ::protobuf::reflect::ProtobufValue for Doc {
 pub struct UpdateDocParams {
     // message fields
     pub doc_id: ::std::string::String,
-    pub data: ::std::vec::Vec<u8>,
+    pub data: ::std::string::String,
     pub rev_id: i64,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
@@ -508,10 +508,10 @@ impl UpdateDocParams {
         ::std::mem::replace(&mut self.doc_id, ::std::string::String::new())
     }
 
-    // bytes data = 2;
+    // string data = 2;
 
 
-    pub fn get_data(&self) -> &[u8] {
+    pub fn get_data(&self) -> &str {
         &self.data
     }
     pub fn clear_data(&mut self) {
@@ -519,19 +519,19 @@ impl UpdateDocParams {
     }
 
     // Param is passed by value, moved
-    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
+    pub fn set_data(&mut self, v: ::std::string::String) {
         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> {
+    pub fn mut_data(&mut self) -> &mut ::std::string::String {
         &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())
+    pub fn take_data(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.data, ::std::string::String::new())
     }
 
     // int64 rev_id = 3;
@@ -563,7 +563,7 @@ impl ::protobuf::Message for UpdateDocParams {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.doc_id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
                 },
                 3 => {
                     if wire_type != ::protobuf::wire_format::WireTypeVarint {
@@ -588,7 +588,7 @@ impl ::protobuf::Message for UpdateDocParams {
             my_size += ::protobuf::rt::string_size(1, &self.doc_id);
         }
         if !self.data.is_empty() {
-            my_size += ::protobuf::rt::bytes_size(2, &self.data);
+            my_size += ::protobuf::rt::string_size(2, &self.data);
         }
         if self.rev_id != 0 {
             my_size += ::protobuf::rt::value_size(3, self.rev_id, ::protobuf::wire_format::WireTypeVarint);
@@ -603,7 +603,7 @@ impl ::protobuf::Message for UpdateDocParams {
             os.write_string(1, &self.doc_id)?;
         }
         if !self.data.is_empty() {
-            os.write_bytes(2, &self.data)?;
+            os.write_string(2, &self.data)?;
         }
         if self.rev_id != 0 {
             os.write_int64(3, self.rev_id)?;
@@ -651,7 +651,7 @@ impl ::protobuf::Message for UpdateDocParams {
                 |m: &UpdateDocParams| { &m.doc_id },
                 |m: &mut UpdateDocParams| { &mut m.doc_id },
             ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "data",
                 |m: &UpdateDocParams| { &m.data },
                 |m: &mut UpdateDocParams| { &mut m.data },
@@ -700,7 +700,7 @@ impl ::protobuf::reflect::ProtobufValue for UpdateDocParams {
 pub struct DocDelta {
     // message fields
     pub doc_id: ::std::string::String,
-    pub data: ::std::vec::Vec<u8>,
+    pub data: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -743,10 +743,10 @@ impl DocDelta {
         ::std::mem::replace(&mut self.doc_id, ::std::string::String::new())
     }
 
-    // bytes data = 2;
+    // string data = 2;
 
 
-    pub fn get_data(&self) -> &[u8] {
+    pub fn get_data(&self) -> &str {
         &self.data
     }
     pub fn clear_data(&mut self) {
@@ -754,19 +754,19 @@ impl DocDelta {
     }
 
     // Param is passed by value, moved
-    pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
+    pub fn set_data(&mut self, v: ::std::string::String) {
         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> {
+    pub fn mut_data(&mut self) -> &mut ::std::string::String {
         &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())
+    pub fn take_data(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.data, ::std::string::String::new())
     }
 }
 
@@ -783,7 +783,7 @@ impl ::protobuf::Message for DocDelta {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.doc_id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -801,7 +801,7 @@ impl ::protobuf::Message for DocDelta {
             my_size += ::protobuf::rt::string_size(1, &self.doc_id);
         }
         if !self.data.is_empty() {
-            my_size += ::protobuf::rt::bytes_size(2, &self.data);
+            my_size += ::protobuf::rt::string_size(2, &self.data);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -813,7 +813,7 @@ impl ::protobuf::Message for DocDelta {
             os.write_string(1, &self.doc_id)?;
         }
         if !self.data.is_empty() {
-            os.write_bytes(2, &self.data)?;
+            os.write_string(2, &self.data)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -858,7 +858,7 @@ impl ::protobuf::Message for DocDelta {
                 |m: &DocDelta| { &m.doc_id },
                 |m: &mut DocDelta| { &mut m.doc_id },
             ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "data",
                 |m: &DocDelta| { &m.data },
                 |m: &mut DocDelta| { &mut m.data },
@@ -1058,47 +1058,47 @@ 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(\x0cR\x04data\"@\n\x03Doc\x12\
+    R\x02id\x12\x12\n\x04data\x18\x02\x20\x01(\tR\x04data\"@\n\x03Doc\x12\
     \x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04data\x18\x02\x20\x01\
-    (\x0cR\x04data\x12\x15\n\x06rev_id\x18\x03\x20\x01(\x03R\x05revId\"S\n\
-    \x0fUpdateDocParams\x12\x15\n\x06doc_id\x18\x01\x20\x01(\tR\x05docId\x12\
-    \x12\n\x04data\x18\x02\x20\x01(\x0cR\x04data\x12\x15\n\x06rev_id\x18\x03\
-    \x20\x01(\x03R\x05revId\"5\n\x08DocDelta\x12\x15\n\x06doc_id\x18\x01\x20\
-    \x01(\tR\x05docId\x12\x12\n\x04data\x18\x02\x20\x01(\x0cR\x04data\"'\n\
-    \x0eQueryDocParams\x12\x15\n\x06doc_id\x18\x01\x20\x01(\tR\x05docIdJ\xe7\
-    \x05\n\x06\x12\x04\0\0\x16\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\n\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\x0b\
-    \n\x04\x04\x01\x02\x02\x12\x03\t\x04\x15\n\x0c\n\x05\x04\x01\x02\x02\x05\
-    \x12\x03\t\x04\t\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\t\n\x10\n\x0c\n\
-    \x05\x04\x01\x02\x02\x03\x12\x03\t\x13\x14\n\n\n\x02\x04\x02\x12\x04\x0b\
-    \0\x0f\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0b\x08\x17\n\x0b\n\x04\x04\x02\
+    (\tR\x04data\x12\x15\n\x06rev_id\x18\x03\x20\x01(\x03R\x05revId\"S\n\x0f\
+    UpdateDocParams\x12\x15\n\x06doc_id\x18\x01\x20\x01(\tR\x05docId\x12\x12\
+    \n\x04data\x18\x02\x20\x01(\tR\x04data\x12\x15\n\x06rev_id\x18\x03\x20\
+    \x01(\x03R\x05revId\"5\n\x08DocDelta\x12\x15\n\x06doc_id\x18\x01\x20\x01\
+    (\tR\x05docId\x12\x12\n\x04data\x18\x02\x20\x01(\tR\x04data\"'\n\x0eQuer\
+    yDocParams\x12\x15\n\x06doc_id\x18\x01\x20\x01(\tR\x05docIdJ\xe7\x05\n\
+    \x06\x12\x04\0\0\x16\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\n\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\x0b\n\x04\
+    \x04\x01\x02\x02\x12\x03\t\x04\x15\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\
+    \x03\t\x04\t\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\t\n\x10\n\x0c\n\x05\
+    \x04\x01\x02\x02\x03\x12\x03\t\x13\x14\n\n\n\x02\x04\x02\x12\x04\x0b\0\
+    \x0f\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0b\x08\x17\n\x0b\n\x04\x04\x02\
     \x02\0\x12\x03\x0c\x04\x16\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x0c\x04\
     \n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0c\x0b\x11\n\x0c\n\x05\x04\x02\
     \x02\0\x03\x12\x03\x0c\x14\x15\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\r\x04\
-    \x13\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\r\x04\t\n\x0c\n\x05\x04\x02\
-    \x02\x01\x01\x12\x03\r\n\x0e\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\r\
-    \x11\x12\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\x0e\x04\x15\n\x0c\n\x05\x04\
+    \x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\x02\
+    \x02\x01\x01\x12\x03\r\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\r\
+    \x12\x13\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\x0e\x04\x15\n\x0c\n\x05\x04\
     \x02\x02\x02\x05\x12\x03\x0e\x04\t\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\
     \x03\x0e\n\x10\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\x03\x0e\x13\x14\n\n\n\
     \x02\x04\x03\x12\x04\x10\0\x13\x01\n\n\n\x03\x04\x03\x01\x12\x03\x10\x08\
     \x10\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x11\x04\x16\n\x0c\n\x05\x04\x03\
     \x02\0\x05\x12\x03\x11\x04\n\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x11\
     \x0b\x11\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x11\x14\x15\n\x0b\n\x04\
-    \x04\x03\x02\x01\x12\x03\x12\x04\x13\n\x0c\n\x05\x04\x03\x02\x01\x05\x12\
-    \x03\x12\x04\t\n\x0c\n\x05\x04\x03\x02\x01\x01\x12\x03\x12\n\x0e\n\x0c\n\
-    \x05\x04\x03\x02\x01\x03\x12\x03\x12\x11\x12\n\n\n\x02\x04\x04\x12\x04\
+    \x04\x03\x02\x01\x12\x03\x12\x04\x14\n\x0c\n\x05\x04\x03\x02\x01\x05\x12\
+    \x03\x12\x04\n\n\x0c\n\x05\x04\x03\x02\x01\x01\x12\x03\x12\x0b\x0f\n\x0c\
+    \n\x05\x04\x03\x02\x01\x03\x12\x03\x12\x12\x13\n\n\n\x02\x04\x04\x12\x04\
     \x14\0\x16\x01\n\n\n\x03\x04\x04\x01\x12\x03\x14\x08\x16\n\x0b\n\x04\x04\
     \x04\x02\0\x12\x03\x15\x04\x16\n\x0c\n\x05\x04\x04\x02\0\x05\x12\x03\x15\
     \x04\n\n\x0c\n\x05\x04\x04\x02\0\x01\x12\x03\x15\x0b\x11\n\x0c\n\x05\x04\

+ 108 - 17
rust-lib/flowy-document/src/protobuf/model/revision.rs

@@ -31,6 +31,7 @@ pub struct Revision {
     pub delta: ::std::vec::Vec<u8>,
     pub md5: ::std::string::String,
     pub doc_id: ::std::string::String,
+    pub ty: RevType,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -154,6 +155,21 @@ impl Revision {
     pub fn take_doc_id(&mut self) -> ::std::string::String {
         ::std::mem::replace(&mut self.doc_id, ::std::string::String::new())
     }
+
+    // .RevType ty = 6;
+
+
+    pub fn get_ty(&self) -> RevType {
+        self.ty
+    }
+    pub fn clear_ty(&mut self) {
+        self.ty = RevType::Local;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_ty(&mut self, v: RevType) {
+        self.ty = v;
+    }
 }
 
 impl ::protobuf::Message for Revision {
@@ -188,6 +204,9 @@ impl ::protobuf::Message for Revision {
                 5 => {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.doc_id)?;
                 },
+                6 => {
+                    ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.ty, 6, &mut self.unknown_fields)?
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -215,6 +234,9 @@ impl ::protobuf::Message for Revision {
         if !self.doc_id.is_empty() {
             my_size += ::protobuf::rt::string_size(5, &self.doc_id);
         }
+        if self.ty != RevType::Local {
+            my_size += ::protobuf::rt::enum_size(6, self.ty);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -236,6 +258,9 @@ impl ::protobuf::Message for Revision {
         if !self.doc_id.is_empty() {
             os.write_string(5, &self.doc_id)?;
         }
+        if self.ty != RevType::Local {
+            os.write_enum(6, ::protobuf::ProtobufEnum::value(&self.ty))?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -299,6 +324,11 @@ impl ::protobuf::Message for Revision {
                 |m: &Revision| { &m.doc_id },
                 |m: &mut Revision| { &mut m.doc_id },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<RevType>>(
+                "ty",
+                |m: &Revision| { &m.ty },
+                |m: &mut Revision| { &mut m.ty },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<Revision>(
                 "Revision",
                 fields,
@@ -320,6 +350,7 @@ impl ::protobuf::Clear for Revision {
         self.delta.clear();
         self.md5.clear();
         self.doc_id.clear();
+        self.ty = RevType::Local;
         self.unknown_fields.clear();
     }
 }
@@ -336,27 +367,87 @@ impl ::protobuf::reflect::ProtobufValue for Revision {
     }
 }
 
+#[derive(Clone,PartialEq,Eq,Debug,Hash)]
+pub enum RevType {
+    Local = 0,
+    Remote = 1,
+}
+
+impl ::protobuf::ProtobufEnum for RevType {
+    fn value(&self) -> i32 {
+        *self as i32
+    }
+
+    fn from_i32(value: i32) -> ::std::option::Option<RevType> {
+        match value {
+            0 => ::std::option::Option::Some(RevType::Local),
+            1 => ::std::option::Option::Some(RevType::Remote),
+            _ => ::std::option::Option::None
+        }
+    }
+
+    fn values() -> &'static [Self] {
+        static values: &'static [RevType] = &[
+            RevType::Local,
+            RevType::Remote,
+        ];
+        values
+    }
+
+    fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            ::protobuf::reflect::EnumDescriptor::new_pb_name::<RevType>("RevType", file_descriptor_proto())
+        })
+    }
+}
+
+impl ::std::marker::Copy for RevType {
+}
+
+impl ::std::default::Default for RevType {
+    fn default() -> Self {
+        RevType::Local
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for RevType {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
+    }
+}
+
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0erevision.proto\"\x80\x01\n\x08Revision\x12\x1e\n\x0bbase_rev_id\
+    \n\x0erevision.proto\"\x9a\x01\n\x08Revision\x12\x1e\n\x0bbase_rev_id\
     \x18\x01\x20\x01(\x03R\tbaseRevId\x12\x15\n\x06rev_id\x18\x02\x20\x01(\
     \x03R\x05revId\x12\x14\n\x05delta\x18\x03\x20\x01(\x0cR\x05delta\x12\x10\
     \n\x03md5\x18\x04\x20\x01(\tR\x03md5\x12\x15\n\x06doc_id\x18\x05\x20\x01\
-    (\tR\x05docIdJ\xbd\x02\n\x06\x12\x04\0\0\x08\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\x10\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x1a\n\x0c\n\x05\
-    \x04\0\x02\0\x05\x12\x03\x03\x04\t\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\
-    \x03\n\x15\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x18\x19\n\x0b\n\x04\
-    \x04\0\x02\x01\x12\x03\x04\x04\x15\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\x10\n\x0c\n\x05\
-    \x04\0\x02\x01\x03\x12\x03\x04\x13\x14\n\x0b\n\x04\x04\0\x02\x02\x12\x03\
-    \x05\x04\x14\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x05\x04\t\n\x0c\n\x05\
-    \x04\0\x02\x02\x01\x12\x03\x05\n\x0f\n\x0c\n\x05\x04\0\x02\x02\x03\x12\
-    \x03\x05\x12\x13\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x04\x13\n\x0c\n\
-    \x05\x04\0\x02\x03\x05\x12\x03\x06\x04\n\n\x0c\n\x05\x04\0\x02\x03\x01\
-    \x12\x03\x06\x0b\x0e\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x06\x11\x12\n\
-    \x0b\n\x04\x04\0\x02\x04\x12\x03\x07\x04\x16\n\x0c\n\x05\x04\0\x02\x04\
-    \x05\x12\x03\x07\x04\n\n\x0c\n\x05\x04\0\x02\x04\x01\x12\x03\x07\x0b\x11\
-    \n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\x07\x14\x15b\x06proto3\
+    (\tR\x05docId\x12\x18\n\x02ty\x18\x06\x20\x01(\x0e2\x08.RevTypeR\x02ty*\
+    \x20\n\x07RevType\x12\t\n\x05Local\x10\0\x12\n\n\x06Remote\x10\x01J\xde\
+    \x03\n\x06\x12\x04\0\0\r\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
+    \x04\0\x12\x04\x02\0\t\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x10\n\x0b\
+    \n\x04\x04\0\x02\0\x12\x03\x03\x04\x1a\n\x0c\n\x05\x04\0\x02\0\x05\x12\
+    \x03\x03\x04\t\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\n\x15\n\x0c\n\x05\
+    \x04\0\x02\0\x03\x12\x03\x03\x18\x19\n\x0b\n\x04\x04\0\x02\x01\x12\x03\
+    \x04\x04\x15\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\x10\n\x0c\n\x05\x04\0\x02\x01\x03\x12\
+    \x03\x04\x13\x14\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\
+    \x05\x04\0\x02\x02\x05\x12\x03\x05\x04\t\n\x0c\n\x05\x04\0\x02\x02\x01\
+    \x12\x03\x05\n\x0f\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x12\x13\n\
+    \x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x04\x13\n\x0c\n\x05\x04\0\x02\x03\
+    \x05\x12\x03\x06\x04\n\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\x0b\x0e\
+    \n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x06\x11\x12\n\x0b\n\x04\x04\0\x02\
+    \x04\x12\x03\x07\x04\x16\n\x0c\n\x05\x04\0\x02\x04\x05\x12\x03\x07\x04\n\
+    \n\x0c\n\x05\x04\0\x02\x04\x01\x12\x03\x07\x0b\x11\n\x0c\n\x05\x04\0\x02\
+    \x04\x03\x12\x03\x07\x14\x15\n\x0b\n\x04\x04\0\x02\x05\x12\x03\x08\x04\
+    \x13\n\x0c\n\x05\x04\0\x02\x05\x06\x12\x03\x08\x04\x0b\n\x0c\n\x05\x04\0\
+    \x02\x05\x01\x12\x03\x08\x0c\x0e\n\x0c\n\x05\x04\0\x02\x05\x03\x12\x03\
+    \x08\x11\x12\n\n\n\x02\x05\0\x12\x04\n\0\r\x01\n\n\n\x03\x05\0\x01\x12\
+    \x03\n\x05\x0c\n\x0b\n\x04\x05\0\x02\0\x12\x03\x0b\x04\x0e\n\x0c\n\x05\
+    \x05\0\x02\0\x01\x12\x03\x0b\x04\t\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\
+    \x0b\x0c\r\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x0c\x04\x0f\n\x0c\n\x05\x05\
+    \0\x02\x01\x01\x12\x03\x0c\x04\n\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\
+    \x0c\r\x0eb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -2,21 +2,21 @@ syntax = "proto3";
 
 message CreateDocParams {
     string id = 1;
-    bytes data = 2;
+    string data = 2;
 }
 message Doc {
     string id = 1;
-    bytes data = 2;
+    string data = 2;
     int64 rev_id = 3;
 }
 message UpdateDocParams {
     string doc_id = 1;
-    bytes data = 2;
+    string data = 2;
     int64 rev_id = 3;
 }
 message DocDelta {
     string doc_id = 1;
-    bytes data = 2;
+    string data = 2;
 }
 message QueryDocParams {
     string doc_id = 1;

+ 5 - 0
rust-lib/flowy-document/src/protobuf/proto/revision.proto

@@ -6,4 +6,9 @@ message Revision {
     bytes delta = 3;
     string md5 = 4;
     string doc_id = 5;
+    RevType ty = 6;
+}
+enum RevType {
+    Local = 0;
+    Remote = 1;
 }

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

@@ -14,6 +14,13 @@ pub(crate) struct DocCache {
 impl DocCache {
     pub(crate) fn new() -> Self { Self { inner: DashMap::new() } }
 
+    pub(crate) fn all_docs(&self) -> Vec<Arc<EditDocContext>> {
+        self.inner
+            .iter()
+            .map(|kv| kv.value().clone())
+            .collect::<Vec<Arc<EditDocContext>>>()
+    }
+
     pub(crate) fn set(&self, doc: Arc<EditDocContext>) {
         let doc_id = doc.id.clone();
         if self.inner.contains_key(&doc_id) {

+ 43 - 10
rust-lib/flowy-document/src/services/doc/doc_controller.rs

@@ -2,16 +2,21 @@ use crate::{
     entities::doc::{CreateDocParams, Doc, DocDelta, QueryDocParams, UpdateDocParams},
     errors::{internal_error, DocError},
     module::DocumentUser,
-    services::{cache::DocCache, doc::edit_doc_context::EditDocContext, server::Server, ws::WsDocumentManager},
+    services::{
+        cache::DocCache,
+        doc::{edit_doc_context::EditDocContext, rev_manager::RevisionManager},
+        server::Server,
+        ws::WsDocumentManager,
+    },
     sql_tables::doc::{DocTable, DocTableSql, OpTableSql},
 };
 use bytes::Bytes;
 use flowy_database::{ConnectionPool, SqliteConnection};
-
-use crate::services::doc::rev_manager::RevisionManager;
+use flowy_infra::future::{wrap_future, FnFuture};
 use flowy_ot::core::Delta;
 use parking_lot::RwLock;
 use std::sync::Arc;
+use tokio::time::{interval, Duration};
 
 pub(crate) struct DocController {
     server: Server,
@@ -27,14 +32,23 @@ impl DocController {
         let doc_sql = Arc::new(DocTableSql {});
         let op_sql = Arc::new(OpTableSql {});
         let cache = Arc::new(DocCache::new());
-        Self {
+
+        let controller = Self {
             server,
             doc_sql,
             op_sql,
             user,
             ws,
-            cache,
-        }
+            cache: cache.clone(),
+        };
+
+        // tokio::spawn(async move {
+        //     tokio::select! {
+        //         _ = event_loop(cache.clone()) => {},
+        //     }
+        // });
+
+        controller
     }
 
     #[tracing::instrument(skip(self, conn), err)]
@@ -49,7 +63,11 @@ impl DocController {
     }
 
     #[tracing::instrument(level = "debug", skip(self, pool), err)]
-    pub(crate) async fn open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Arc<EditDocContext>, DocError> {
+    pub(crate) async fn open(
+        &self,
+        params: QueryDocParams,
+        pool: Arc<ConnectionPool>,
+    ) -> Result<Arc<EditDocContext>, DocError> {
         if self.cache.is_opened(&params.doc_id) == false {
             return match self._open(params, pool).await {
                 Ok(doc) => Ok(doc),
@@ -81,7 +99,7 @@ impl DocController {
     #[tracing::instrument(level = "debug", skip(self, delta), err)]
     pub(crate) fn edit_doc(&self, delta: DocDelta) -> Result<Doc, DocError> {
         let edit_doc_ctx = self.cache.get(&delta.doc_id)?;
-        let _ = edit_doc_ctx.apply_local_delta(Bytes::from(delta.data))?;
+        let _ = edit_doc_ctx.compose_local_delta(Bytes::from(delta.data))?;
         Ok(edit_doc_ctx.doc())
     }
 }
@@ -104,7 +122,11 @@ impl DocController {
     }
 
     #[tracing::instrument(level = "debug", skip(self, pool), err)]
-    async fn read_doc_from_server(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Arc<EditDocContext>, DocError> {
+    async fn read_doc_from_server(
+        &self,
+        params: QueryDocParams,
+        pool: Arc<ConnectionPool>,
+    ) -> Result<Arc<EditDocContext>, DocError> {
         let token = self.user.token()?;
         match self.server.read_doc(&token, params).await? {
             None => Err(DocError::not_found()),
@@ -151,7 +173,7 @@ impl DocController {
         // Opti: require upgradable_read lock and then upgrade to write lock using
         // RwLockUpgradableReadGuard::upgrade(xx) of ws
         let doc_id = doc.id.clone();
-        let delta = Delta::from_bytes(doc.data)?;
+        let delta = Delta::from_bytes(&doc.data)?;
         let ws_sender = self.ws.read().sender();
         let rev_manager = RevisionManager::new(&doc_id, doc.rev_id, self.op_sql.clone(), pool, ws_sender);
         let edit_ctx = Arc::new(EditDocContext::new(&doc_id, delta, rev_manager)?);
@@ -160,3 +182,14 @@ impl DocController {
         Ok(edit_ctx)
     }
 }
+
+#[allow(dead_code)]
+fn event_loop(_cache: Arc<DocCache>) -> FnFuture<()> {
+    let mut i = interval(Duration::from_secs(3));
+    wrap_future(async move {
+        loop {
+            // cache.all_docs().iter().for_each(|doc| doc.tick());
+            i.tick().await;
+        }
+    })
+}

+ 30 - 37
rust-lib/flowy-document/src/services/doc/document/document.rs

@@ -2,7 +2,7 @@ use crate::{
     errors::DocError,
     services::doc::{view::View, History, UndoResult, RECORD_THRESHOLD},
 };
-use bytes::Bytes;
+
 use flowy_ot::core::*;
 
 pub trait DocumentData {
@@ -57,10 +57,29 @@ impl Document {
 
     pub fn set_delta(&mut self, data: Delta) { self.delta = data; }
 
-    pub fn apply_delta(&mut self, delta: Delta) -> Result<(), DocError> {
-        log::trace!("Apply delta: {}", delta);
-        let _ = self.add_delta(&delta)?;
-        log::debug!("Document: {}", self.to_json());
+    pub fn compose_delta(&mut self, delta: &Delta) -> Result<(), DocError> {
+        let composed_delta = self.delta.compose(delta)?;
+        let mut undo_delta = delta.invert(&self.delta);
+
+        let now = chrono::Utc::now().timestamp_millis() as usize;
+        if now - self.last_edit_time < RECORD_THRESHOLD {
+            if let Some(last_delta) = self.history.undo() {
+                log::trace!("compose previous change");
+                log::trace!("current = {}", undo_delta);
+                log::trace!("previous = {}", last_delta);
+                undo_delta = undo_delta.compose(&last_delta)?;
+            }
+        } else {
+            self.last_edit_time = now;
+        }
+
+        log::trace!("👉 receive change undo: {}", undo_delta);
+        if !undo_delta.is_empty() {
+            self.history.record(undo_delta);
+        }
+
+        log::trace!("document delta: {}", &composed_delta);
+        self.delta = composed_delta;
         Ok(())
     }
 
@@ -71,7 +90,7 @@ impl Document {
         let text = data.into_string()?;
         let delta = self.view.insert(&self.delta, &text, interval)?;
         log::trace!("👉 receive change: {}", delta);
-        self.add_delta(&delta)?;
+        self.compose_delta(&delta)?;
         Ok(delta)
     }
 
@@ -81,19 +100,19 @@ impl Document {
         let delete = self.view.delete(&self.delta, interval)?;
         if !delete.is_empty() {
             log::trace!("👉 receive change: {}", delete);
-            let _ = self.add_delta(&delete)?;
+            let _ = self.compose_delta(&delete)?;
         }
         Ok(delete)
     }
 
-    pub fn format(&mut self, interval: Interval, attribute: Attribute) -> Result<(), DocError> {
+    pub fn format(&mut self, interval: Interval, attribute: Attribute) -> Result<Delta, DocError> {
         let _ = validate_interval(&self.delta, &interval)?;
         log::trace!("format with {} at {}", attribute, interval);
         let format_delta = self.view.format(&self.delta, attribute.clone(), interval).unwrap();
 
         log::trace!("👉 receive change: {}", format_delta);
-        self.add_delta(&format_delta)?;
-        Ok(())
+        self.compose_delta(&format_delta)?;
+        Ok(format_delta)
     }
 
     pub fn replace<T: DocumentData>(&mut self, interval: Interval, data: T) -> Result<Delta, DocError> {
@@ -103,7 +122,7 @@ impl Document {
         if !text.is_empty() {
             delta = self.view.insert(&self.delta, &text, interval)?;
             log::trace!("👉 receive change: {}", delta);
-            self.add_delta(&delta)?;
+            self.compose_delta(&delta)?;
         }
 
         if !interval.is_empty() {
@@ -148,32 +167,6 @@ impl Document {
 }
 
 impl Document {
-    fn add_delta(&mut self, delta: &Delta) -> Result<(), DocError> {
-        let composed_delta = self.delta.compose(delta)?;
-        let mut undo_delta = delta.invert(&self.delta);
-
-        let now = chrono::Utc::now().timestamp_millis() as usize;
-        if now - self.last_edit_time < RECORD_THRESHOLD {
-            if let Some(last_delta) = self.history.undo() {
-                log::trace!("compose previous change");
-                log::trace!("current = {}", undo_delta);
-                log::trace!("previous = {}", last_delta);
-                undo_delta = undo_delta.compose(&last_delta)?;
-            }
-        } else {
-            self.last_edit_time = now;
-        }
-
-        log::trace!("👉 receive change undo: {}", undo_delta);
-        if !undo_delta.is_empty() {
-            self.history.record(undo_delta);
-        }
-
-        log::trace!("document delta: {}", &composed_delta);
-        self.delta = composed_delta;
-        Ok(())
-    }
-
     fn invert_change(&self, change: &Delta) -> Result<(Delta, Delta), DocError> {
         // c = a.compose(b)
         // d = b.invert(a)

+ 21 - 36
rust-lib/flowy-document/src/services/doc/edit_doc_context.rs

@@ -6,16 +6,16 @@ use crate::{
     errors::*,
     services::{
         doc::{rev_manager::RevisionManager, Document},
-        util::{bytes_to_rev_id, md5},
-        ws::{WsDocumentHandler, WsDocumentSender},
+        util::bytes_to_rev_id,
+        ws::WsDocumentHandler,
     },
-    sql_tables::{OpTable, OpTableSql},
+    sql_tables::{OpTableSql, RevTable},
 };
 use bytes::Bytes;
+
 use flowy_ot::core::Delta;
 use parking_lot::RwLock;
 use std::{convert::TryFrom, sync::Arc};
-use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
 
 pub type DocId = String;
 
@@ -30,49 +30,30 @@ impl EditDocContext {
         let id = doc_id.to_owned();
         let rev_manager = Arc::new(rev_manager);
         let document = Arc::new(RwLock::new(Document::from_delta(delta)));
-        let edit_context = Self { id, document, rev_manager };
-        edit_context.composing_delta();
-
+        let edit_context = Self {
+            id,
+            document,
+            rev_manager,
+        };
         Ok(edit_context)
     }
 
     pub(crate) fn doc(&self) -> Doc {
         Doc {
             id: self.id.clone(),
-            data: self.document.read().to_bytes(),
-            rev_id: self.rev_manager.rev(),
+            data: self.document.read().to_json(),
+            rev_id: self.rev_manager.rev_id(),
         }
     }
 
     #[tracing::instrument(level = "debug", skip(self, data), err)]
-    pub(crate) fn apply_local_delta(&self, data: Bytes) -> Result<(), DocError> {
-        let doc_id = self.id.clone();
-        let (base_rev_id, rev_id) = self.rev_manager.next_rev();
-        let revision = Revision::new(base_rev_id, rev_id, data.to_vec(), md5(&data), doc_id);
-
-        let delta = Delta::from_bytes(data.to_vec())?;
-        self.document.write().apply_delta(delta)?;
+    pub(crate) fn compose_local_delta(&self, data: Bytes) -> Result<(), DocError> {
+        let delta = Delta::from_bytes(&data)?;
+        self.document.write().compose_delta(&delta)?;
+        self.rev_manager.add_delta(data);
 
-        self.rev_manager.add_local(revision);
         Ok(())
     }
-
-    fn composing_delta(&self) {
-        let rev_manager = self.rev_manager.clone();
-        let document = self.document.clone();
-        tokio::spawn(async move {
-            let notified = rev_manager.notified();
-            tokio::select! {
-                _ = notified => {
-                    if let Some(delta) = rev_manager.next_compose_delta() {
-                        log::info!("😁receive delta: {:?}", delta);
-                        document.write().apply_delta(delta).unwrap();
-                        log::info!("😁Document: {:?}", document.read().to_plain_string());
-                    }
-                }
-            }
-        });
-    }
 }
 
 impl WsDocumentHandler for EditDocContext {
@@ -82,14 +63,18 @@ impl WsDocumentHandler for EditDocContext {
                 WsDataType::Rev => {
                     let bytes = Bytes::from(doc_data.data);
                     let revision = Revision::try_from(bytes)?;
-                    self.rev_manager.add_remote(revision);
+                    self.rev_manager.add_revision(revision);
+                    self.rev_manager.next_compose_delta(|delta| {
+                        let _ = self.document.write().compose_delta(delta)?;
+                        log::debug!("😁Document: {:?}", self.document.read().to_plain_string());
+                        Ok(())
+                    });
                 },
                 WsDataType::Acked => {
                     let rev_id = bytes_to_rev_id(doc_data.data)?;
                     self.rev_manager.remove(rev_id);
                 },
             }
-
             Result::<(), DocError>::Ok(())
         };
 

+ 70 - 39
rust-lib/flowy-document/src/services/doc/rev_manager.rs

@@ -1,21 +1,20 @@
 use crate::{
-    entities::{
-        doc::Revision,
-        ws::{WsDataType, WsDocumentData},
-    },
+    entities::doc::{RevType, Revision},
     errors::{internal_error, DocError},
     services::{
-        util::{bytes_to_rev_id, RevIdCounter},
+        util::{md5, RevIdCounter},
         ws::{WsDocumentHandler, WsDocumentSender},
     },
-    sql_tables::{OpTable, OpTableSql},
+    sql_tables::{OpTableSql, RevTable},
 };
 use bytes::Bytes;
 use flowy_database::ConnectionPool;
-use flowy_infra::future::wrap_future;
 use flowy_ot::core::Delta;
 use parking_lot::RwLock;
-use std::{collections::BTreeMap, sync::Arc};
+use std::{
+    collections::{BTreeMap, VecDeque},
+    sync::Arc,
+};
 use tokio::sync::{futures::Notified, Notify};
 
 pub struct RevisionManager {
@@ -24,7 +23,8 @@ pub struct RevisionManager {
     pool: Arc<ConnectionPool>,
     rev_id_counter: RevIdCounter,
     ws_sender: Arc<dyn WsDocumentSender>,
-    rev_cache: RwLock<BTreeMap<i64, Revision>>,
+    local_rev_cache: Arc<RwLock<BTreeMap<i64, Revision>>>,
+    remote_rev_cache: RwLock<VecDeque<Revision>>,
     notify: Notify,
 }
 
@@ -37,67 +37,98 @@ impl RevisionManager {
         ws_sender: Arc<dyn WsDocumentSender>,
     ) -> Self {
         let rev_id_counter = RevIdCounter::new(rev_id);
-        let rev_cache = RwLock::new(BTreeMap::new());
+        let local_rev_cache = Arc::new(RwLock::new(BTreeMap::new()));
+        let remote_rev_cache = RwLock::new(VecDeque::new());
         Self {
             doc_id: doc_id.to_owned(),
             op_sql,
             pool,
             rev_id_counter,
             ws_sender,
-            rev_cache,
+            local_rev_cache,
+            remote_rev_cache,
             notify: Notify::new(),
         }
     }
 
-    pub fn next_compose_delta(&self) -> Option<Delta> {
-        // let delta = Delta::from_bytes(revision.delta)?;
-        //
-        // log::debug!("Remote delta: {:?}", delta);
+    pub fn next_compose_delta<F>(&self, mut f: F)
+    where
+        F: FnMut(&Delta) -> Result<(), DocError>,
+    {
+        if let Some(rev) = self.remote_rev_cache.write().pop_front() {
+            match Delta::from_bytes(&rev.delta) {
+                Ok(delta) => match f(&delta) {
+                    Ok(_) => {},
+                    Err(e) => {
+                        log::error!("{}", e);
+                        self.remote_rev_cache.write().push_front(rev);
+                    },
+                },
+                Err(_) => {},
+            }
+        }
     }
 
-    pub fn notified(&self) -> Notified { self.notify.notified() }
-
-    pub fn next_rev(&self) -> (i64, i64) {
-        let cur = self.rev_id_counter.value();
-        let next = self.rev_id_counter.next();
-        (cur, next)
+    #[tracing::instrument(level = "debug", skip(self, delta_data))]
+    pub fn add_delta(&self, delta_data: Bytes) -> Result<(), DocError> {
+        let (base_rev_id, rev_id) = self.next_rev_id();
+        let revision = Revision::new(
+            base_rev_id,
+            rev_id,
+            delta_data.to_vec(),
+            md5(&delta_data),
+            self.doc_id.clone(),
+            RevType::Local,
+        );
+        let _ = self.add_revision(revision)?;
+        Ok(())
     }
 
-    pub fn rev(&self) -> i64 { self.rev_id_counter.value() }
-
-    pub fn add_local(&self, revision: Revision) -> Result<(), DocError> {
-        self.rev_cache.write().insert(revision.rev_id, revision.clone());
-        match self.ws_sender.send(revision.into()) {
-            Ok(_) => {},
-            Err(e) => {
-                log::error!("Send delta failed: {:?}", e);
+    #[tracing::instrument(level = "debug", skip(self, revision))]
+    pub fn add_revision(&self, revision: Revision) -> Result<(), DocError> {
+        match revision.ty {
+            RevType::Local => {
+                self.local_rev_cache.write().insert(revision.rev_id, revision.clone());
+                // self.save_revision(revision.clone());
+                match self.ws_sender.send(revision.into()) {
+                    Ok(_) => {},
+                    Err(e) => {
+                        log::error!("Send delta failed: {:?}", e);
+                    },
+                }
+            },
+            RevType::Remote => {
+                self.remote_rev_cache.write().push_back(revision);
+                self.notify.notify_waiters();
             },
         }
-        // self.save_revision(revision.clone());
-        Ok(())
-    }
 
-    #[tracing::instrument(level = "debug", skip(self, revision))]
-    pub fn add_remote(&self, revision: Revision) -> Result<(), DocError> {
-        self.rev_cache.write().insert(revision.rev_id, revision);
-        // self.save_revision(revision.clone());
-        self.notify.notify_waiters();
         Ok(())
     }
 
     pub fn remove(&self, rev_id: i64) -> Result<(), DocError> {
-        self.rev_cache.write().remove(&rev_id);
+        self.local_rev_cache.write().remove(&rev_id);
         // self.delete_revision(rev_id);
         Ok(())
     }
 
+    pub fn rev_notified(&self) -> Notified { self.notify.notified() }
+
+    pub fn next_rev_id(&self) -> (i64, i64) {
+        let cur = self.rev_id_counter.value();
+        let next = self.rev_id_counter.next();
+        (cur, next)
+    }
+
+    pub fn rev_id(&self) -> i64 { self.rev_id_counter.value() }
+
     fn save_revision(&self, revision: Revision) {
         let op_sql = self.op_sql.clone();
         let pool = self.pool.clone();
         tokio::spawn(async move {
             let conn = &*pool.get().map_err(internal_error).unwrap();
             let result = conn.immediate_transaction::<_, DocError, _>(|| {
-                let op_table: OpTable = revision.into();
+                let op_table: RevTable = revision.into();
                 let _ = op_sql.create_op_table(op_table, conn).unwrap();
                 Ok(())
             });

+ 9 - 9
rust-lib/flowy-document/src/sql_tables/doc/doc_op_sql.rs

@@ -1,35 +1,35 @@
 use crate::{
     errors::DocError,
-    sql_tables::doc::{OpChangeset, OpTable},
+    sql_tables::doc::{RevChangeset, RevTable},
 };
 use flowy_database::{
     prelude::*,
-    schema::{op_table, op_table::dsl},
+    schema::{rev_table, rev_table::dsl},
     SqliteConnection,
 };
 
 pub struct OpTableSql {}
 
 impl OpTableSql {
-    pub(crate) fn create_op_table(&self, op_table: OpTable, conn: &SqliteConnection) -> Result<(), DocError> {
-        let _ = diesel::insert_into(op_table::table).values(op_table).execute(conn)?;
+    pub(crate) fn create_op_table(&self, op_table: RevTable, conn: &SqliteConnection) -> Result<(), DocError> {
+        let _ = diesel::insert_into(rev_table::table).values(op_table).execute(conn)?;
         Ok(())
     }
 
-    pub(crate) fn update_op_table(&self, changeset: OpChangeset, conn: &SqliteConnection) -> Result<(), DocError> {
-        let filter = dsl::op_table.filter(op_table::dsl::rev_id.eq(changeset.rev_id));
+    pub(crate) fn update_op_table(&self, changeset: RevChangeset, conn: &SqliteConnection) -> Result<(), DocError> {
+        let filter = dsl::rev_table.filter(rev_table::dsl::rev_id.eq(changeset.rev_id));
         let affected_row = diesel::update(filter).set(changeset).execute(conn)?;
         debug_assert_eq!(affected_row, 1);
         Ok(())
     }
 
-    pub(crate) fn read_op_table(&self, conn: &SqliteConnection) -> Result<Vec<OpTable>, DocError> {
-        let ops = dsl::op_table.load::<OpTable>(conn)?;
+    pub(crate) fn read_op_table(&self, conn: &SqliteConnection) -> Result<Vec<RevTable>, DocError> {
+        let ops = dsl::rev_table.load::<RevTable>(conn)?;
         Ok(ops)
     }
 
     pub(crate) fn delete_op_table(&self, rev_id: i64, conn: &SqliteConnection) -> Result<(), DocError> {
-        let filter = dsl::op_table.filter(op_table::dsl::rev_id.eq(rev_id));
+        let filter = dsl::rev_table.filter(rev_table::dsl::rev_id.eq(rev_id));
         let affected_row = diesel::delete(filter).execute(conn)?;
         debug_assert_eq!(affected_row, 1);
         Ok(())

+ 59 - 23
rust-lib/flowy-document/src/sql_tables/doc/doc_op_table.rs

@@ -1,68 +1,104 @@
-use crate::entities::doc::Revision;
+use crate::entities::doc::{RevType, Revision};
 use diesel::sql_types::Integer;
-use flowy_database::schema::op_table;
+use flowy_database::schema::rev_table;
 
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
-#[table_name = "op_table"]
+#[table_name = "rev_table"]
 #[primary_key(doc_id)]
-pub(crate) struct OpTable {
+pub(crate) struct RevTable {
     pub(crate) doc_id: String,
     pub(crate) base_rev_id: i64,
     pub(crate) rev_id: i64,
     pub(crate) data: Vec<u8>,
     pub(crate) md5: String,
-    pub(crate) state: OpState,
+    pub(crate) state: RevState,
+    pub(crate) ty: RevTableType,
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)]
 #[repr(i32)]
 #[sql_type = "Integer"]
-pub enum OpState {
+pub enum RevState {
     Local = 0,
     Acked = 1,
 }
 
-impl std::default::Default for OpState {
-    fn default() -> Self { OpState::Local }
+impl std::default::Default for RevState {
+    fn default() -> Self { RevState::Local }
 }
 
-impl std::convert::From<i32> for OpState {
+impl std::convert::From<i32> for RevState {
     fn from(value: i32) -> Self {
         match value {
-            0 => OpState::Local,
-            1 => OpState::Acked,
+            0 => RevState::Local,
+            1 => RevState::Acked,
             o => {
-                log::error!("Unsupported view type {}, fallback to ViewType::Docs", o);
-                OpState::Local
+                log::error!("Unsupported rev state {}, fallback to RevState::Local", o);
+                RevState::Local
             },
         }
     }
 }
-
-impl OpState {
+impl RevState {
     pub fn value(&self) -> i32 { *self as i32 }
 }
+impl_sql_integer_expression!(RevState);
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)]
+#[repr(i32)]
+#[sql_type = "Integer"]
+pub enum RevTableType {
+    Local  = 0,
+    Remote = 1,
+}
+
+impl std::default::Default for RevTableType {
+    fn default() -> Self { RevTableType::Local }
+}
 
-impl_sql_integer_expression!(OpState);
+impl std::convert::From<i32> for RevTableType {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => RevTableType::Local,
+            1 => RevTableType::Remote,
+            o => {
+                log::error!("Unsupported rev type {}, fallback to RevTableType::Local", o);
+                RevTableType::Local
+            },
+        }
+    }
+}
+impl RevTableType {
+    pub fn value(&self) -> i32 { *self as i32 }
+}
+impl_sql_integer_expression!(RevTableType);
 
 #[derive(AsChangeset, Identifiable, Default, Debug)]
-#[table_name = "op_table"]
+#[table_name = "rev_table"]
 #[primary_key(doc_id)]
-pub(crate) struct OpChangeset {
+pub(crate) struct RevChangeset {
     pub(crate) doc_id: String,
     pub(crate) rev_id: i64,
-    pub(crate) state: Option<OpState>,
+    pub(crate) state: Option<RevState>,
 }
 
-impl std::convert::Into<OpTable> for Revision {
-    fn into(self) -> OpTable {
-        OpTable {
+impl std::convert::Into<RevTable> for Revision {
+    fn into(self) -> RevTable {
+        RevTable {
             doc_id: self.doc_id,
             base_rev_id: self.base_rev_id,
             rev_id: self.rev_id,
             data: self.delta,
             md5: self.md5,
-            state: OpState::Local,
+            state: RevState::Local,
+            ty: rev_ty_to_rev_state(self.ty),
         }
     }
 }
+
+fn rev_ty_to_rev_state(ty: RevType) -> RevTableType {
+    match ty {
+        RevType::Local => RevTableType::Local,
+        RevType::Remote => RevTableType::Remote,
+    }
+}

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

@@ -5,7 +5,7 @@ use flowy_database::schema::doc_table;
 #[table_name = "doc_table"]
 pub(crate) struct DocTable {
     pub(crate) id: String,
-    pub(crate) data: Vec<u8>,
+    pub(crate) data: String,
     pub(crate) revision: i64,
 }
 
@@ -23,7 +23,7 @@ impl DocTable {
 #[table_name = "doc_table"]
 pub(crate) struct DocTableChangeset {
     pub id: String,
-    pub data: Vec<u8>,
+    pub data: String,
 }
 
 impl DocTableChangeset {

+ 98 - 86
rust-lib/flowy-document/tests/editor/attribute_test.rs

@@ -7,7 +7,7 @@ fn attributes_bold_added() {
     let ops = vec![
         Insert(0, "123456", 0),
         Bold(0, Interval::new(3, 5), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"123"},
@@ -24,9 +24,9 @@ fn attributes_bold_added_and_invert_all() {
     let ops = vec![
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
-        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
         Bold(0, Interval::new(0, 3), false),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -36,9 +36,9 @@ fn attributes_bold_added_and_invert_partial_suffix() {
     let ops = vec![
         Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 4), true),
-        AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
         Bold(0, Interval::new(2, 4), false),
-        AssertOpsJson(0, r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#),
+        AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -48,11 +48,11 @@ fn attributes_bold_added_and_invert_partial_suffix2() {
     let ops = vec![
         Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 4), true),
-        AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
         Bold(0, Interval::new(2, 4), false),
-        AssertOpsJson(0, r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#),
+        AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#),
         Bold(0, Interval::new(2, 4), true),
-        AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -62,19 +62,22 @@ fn attributes_bold_added_with_new_line() {
     let ops = vec![
         Insert(0, "123456", 0),
         Bold(0, Interval::new(0, 6), true),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
+        AssertDocJson(
+            0,
+            r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
+        ),
         Insert(0, "\n", 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"},{"insert":"456","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
         ),
         Insert(0, "\n", 4),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n\n"},{"insert":"456","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
         ),
         Insert(0, "a", 4),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\na\n"},{"insert":"456","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
         ),
@@ -87,9 +90,9 @@ fn attributes_bold_added_and_invert_partial_prefix() {
     let ops = vec![
         Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 4), true),
-        AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#),
         Bold(0, Interval::new(0, 2), false),
-        AssertOpsJson(0, r#"[{"insert":"12"},{"insert":"34","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"12"},{"insert":"34","attributes":{"bold":"true"}}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -99,9 +102,9 @@ fn attributes_bold_added_consecutive() {
     let ops = vec![
         Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 1), true),
-        AssertOpsJson(0, r#"[{"insert":"1","attributes":{"bold":"true"}},{"insert":"234"}]"#),
+        AssertDocJson(0, r#"[{"insert":"1","attributes":{"bold":"true"}},{"insert":"234"}]"#),
         Bold(0, Interval::new(1, 2), true),
-        AssertOpsJson(0, r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#),
+        AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":"true"}},{"insert":"34"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -112,12 +115,12 @@ fn attributes_bold_added_italic() {
         Insert(0, "1234", 0),
         Bold(0, Interval::new(0, 4), true),
         Italic(0, Interval::new(0, 4), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"1234","attributes":{"italic":"true","bold":"true"}},{"insert":"\n"}]"#,
         ),
         Insert(0, "5678", 4),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"12345678","attributes":{"bold":"true","italic":"true"}},{"insert":"\n"}]"#,
         ),
@@ -130,9 +133,9 @@ fn attributes_bold_added_italic2() {
     let ops = vec![
         Insert(0, "123456", 0),
         Bold(0, Interval::new(0, 6), true),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
         Italic(0, Interval::new(0, 2), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"italic":"true","bold":"true"}},
@@ -140,7 +143,7 @@ fn attributes_bold_added_italic2() {
             "#,
         ),
         Italic(0, Interval::new(4, 6), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"italic":"true","bold":"true"}},
@@ -159,7 +162,7 @@ fn attributes_bold_added_italic3() {
         Insert(0, "123456789", 0),
         Bold(0, Interval::new(0, 5), true),
         Italic(0, Interval::new(0, 2), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"bold":"true","italic":"true"}},
@@ -167,7 +170,7 @@ fn attributes_bold_added_italic3() {
             "#,
         ),
         Italic(0, Interval::new(2, 4), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"1234","attributes":{"bold":"true","italic":"true"}},
@@ -176,7 +179,7 @@ fn attributes_bold_added_italic3() {
             "#,
         ),
         Bold(0, Interval::new(7, 9), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"1234","attributes":{"bold":"true","italic":"true"}},
@@ -196,7 +199,7 @@ fn attributes_bold_added_italic_delete() {
         Insert(0, "123456789", 0),
         Bold(0, Interval::new(0, 5), true),
         Italic(0, Interval::new(0, 2), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"italic":"true","bold":"true"}},
@@ -204,14 +207,14 @@ fn attributes_bold_added_italic_delete() {
             "#,
         ),
         Italic(0, Interval::new(2, 4), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"1234","attributes":{"bold":"true","italic":"true"}}
             ,{"insert":"5","attributes":{"bold":"true"}},{"insert":"6789"}]"#,
         ),
         Bold(0, Interval::new(7, 9), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"1234","attributes":{"bold":"true","italic":"true"}},
@@ -220,7 +223,7 @@ fn attributes_bold_added_italic_delete() {
             "#,
         ),
         Delete(0, Interval::new(0, 5)),
-        AssertOpsJson(0, r#"[{"insert":"67"},{"insert":"89","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"67"},{"insert":"89","attributes":{"bold":"true"}}]"#),
     ];
 
     TestBuilder::new().run_script::<PlainDoc>(ops);
@@ -230,9 +233,9 @@ fn attributes_bold_added_italic_delete() {
 fn attributes_merge_inserted_text_with_same_attribute() {
     let ops = vec![
         InsertBold(0, "123", Interval::new(0, 3)),
-        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
         InsertBold(0, "456", Interval::new(3, 6)),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -241,12 +244,12 @@ fn attributes_merge_inserted_text_with_same_attribute() {
 fn attributes_compose_attr_attributes_with_attr_attributes_test() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
         InsertBold(1, "7", Interval::new(0, 1)),
-        AssertOpsJson(1, r#"[{"insert":"7","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(1, r#"[{"insert":"7","attributes":{"bold":"true"}}]"#),
         Transform(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#),
-        AssertOpsJson(1, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(1, r#"[{"insert":"1234567","attributes":{"bold":"true"}}]"#),
     ];
 
     TestBuilder::new().run_script::<PlainDoc>(ops);
@@ -259,7 +262,7 @@ fn attributes_compose_attr_attributes_with_attr_attributes_test2() {
         Bold(0, Interval::new(0, 6), true),
         Italic(0, Interval::new(0, 2), true),
         Italic(0, Interval::new(4, 6), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"bold":"true","italic":"true"}},
@@ -268,9 +271,9 @@ fn attributes_compose_attr_attributes_with_attr_attributes_test2() {
             "#,
         ),
         InsertBold(1, "7", Interval::new(0, 1)),
-        AssertOpsJson(1, r#"[{"insert":"7","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(1, r#"[{"insert":"7","attributes":{"bold":"true"}}]"#),
         Transform(0, 1),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"italic":"true","bold":"true"}},
@@ -279,7 +282,7 @@ fn attributes_compose_attr_attributes_with_attr_attributes_test2() {
             {"insert":"7","attributes":{"bold":"true"}}]
             "#,
         ),
-        AssertOpsJson(
+        AssertDocJson(
             1,
             r#"[
             {"insert":"12","attributes":{"italic":"true","bold":"true"}},
@@ -299,12 +302,12 @@ fn attributes_compose_attr_attributes_with_no_attr_attributes_test() {
 
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
         Insert(1, "7", 0),
-        AssertOpsJson(1, r#"[{"insert":"7"}]"#),
+        AssertDocJson(1, r#"[{"insert":"7"}]"#),
         Transform(0, 1),
-        AssertOpsJson(0, expected),
-        AssertOpsJson(1, expected),
+        AssertDocJson(0, expected),
+        AssertDocJson(1, expected),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -313,9 +316,9 @@ fn attributes_compose_attr_attributes_with_no_attr_attributes_test() {
 fn attributes_replace_heading() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
         Delete(0, Interval::new(0, 2)),
-        AssertOpsJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#),
     ];
 
     TestBuilder::new().run_script::<PlainDoc>(ops);
@@ -325,9 +328,9 @@ fn attributes_replace_heading() {
 fn attributes_replace_trailing() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
         Delete(0, Interval::new(5, 6)),
-        AssertOpsJson(0, r#"[{"insert":"12345","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"12345","attributes":{"bold":"true"}}]"#),
     ];
 
     TestBuilder::new().run_script::<PlainDoc>(ops);
@@ -337,11 +340,11 @@ fn attributes_replace_trailing() {
 fn attributes_replace_middle() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
         Delete(0, Interval::new(0, 2)),
-        AssertOpsJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"3456","attributes":{"bold":"true"}}]"#),
         Delete(0, Interval::new(2, 4)),
-        AssertOpsJson(0, r#"[{"insert":"34","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"34","attributes":{"bold":"true"}}]"#),
     ];
 
     TestBuilder::new().run_script::<PlainDoc>(ops);
@@ -351,9 +354,9 @@ fn attributes_replace_middle() {
 fn attributes_replace_all() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
         Delete(0, Interval::new(0, 6)),
-        AssertOpsJson(0, r#"[]"#),
+        AssertDocJson(0, r#"[]"#),
     ];
 
     TestBuilder::new().run_script::<PlainDoc>(ops);
@@ -363,9 +366,9 @@ fn attributes_replace_all() {
 fn attributes_replace_with_text() {
     let ops = vec![
         InsertBold(0, "123456", Interval::new(0, 6)),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
         Replace(0, Interval::new(0, 3), "ab"),
-        AssertOpsJson(0, r#"[{"insert":"ab"},{"insert":"456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"ab"},{"insert":"456","attributes":{"bold":"true"}}]"#),
     ];
 
     TestBuilder::new().run_script::<PlainDoc>(ops);
@@ -376,9 +379,9 @@ fn attributes_header_insert_newline_at_middle() {
     let ops = vec![
         Insert(0, "123456", 0),
         Header(0, Interval::new(0, 6), 1),
-        AssertOpsJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}}]"#),
         Insert(0, "\n", 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123"},{"insert":"\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#,
         ),
@@ -393,17 +396,17 @@ fn attributes_header_insert_double_newline_at_middle() {
         Insert(0, "123456", 0),
         Header(0, Interval::new(0, 6), 1),
         Insert(0, "\n", 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123"},{"insert":"\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#,
         ),
         Insert(0, "\n", 4),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#,
         ),
         Insert(0, "\n", 4),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":1}},{"insert":"\n456"},{"insert":"\n","attributes":{"header":1}}]"#,
         ),
@@ -418,7 +421,7 @@ fn attributes_header_insert_newline_at_trailing() {
         Insert(0, "123456", 0),
         Header(0, Interval::new(0, 6), 1),
         Insert(0, "\n", 6),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}},{"insert":"\n"}]"#,
         ),
@@ -434,7 +437,7 @@ fn attributes_header_insert_double_newline_at_trailing() {
         Header(0, Interval::new(0, 6), 1),
         Insert(0, "\n", 6),
         Insert(0, "\n", 7),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}},{"insert":"\n\n"}]"#,
         ),
@@ -448,7 +451,7 @@ fn attributes_link_added() {
     let ops = vec![
         Insert(0, "123456", 0),
         Link(0, Interval::new(0, 6), "https://appflowy.io"),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
         ),
@@ -463,7 +466,7 @@ fn attributes_link_format_with_bold() {
         Insert(0, "123456", 0),
         Link(0, Interval::new(0, 6), "https://appflowy.io"),
         Bold(0, Interval::new(0, 3), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"123","attributes":{"bold":"true","link":"https://appflowy.io"}},
@@ -481,12 +484,12 @@ fn attributes_link_insert_char_at_head() {
     let ops = vec![
         Insert(0, "123456", 0),
         Link(0, Interval::new(0, 6), "https://appflowy.io"),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
         ),
         Insert(0, "a", 0),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"a"},{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
         ),
@@ -501,7 +504,7 @@ fn attributes_link_insert_char_at_middle() {
         Insert(0, "1256", 0),
         Link(0, Interval::new(0, 4), "https://appflowy.io"),
         Insert(0, "34", 2),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
         ),
@@ -515,12 +518,12 @@ fn attributes_link_insert_char_at_trailing() {
     let ops = vec![
         Insert(0, "123456", 0),
         Link(0, Interval::new(0, 6), "https://appflowy.io"),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
         ),
         Insert(0, "a", 6),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"a\n"}]"#,
         ),
@@ -535,7 +538,7 @@ fn attributes_link_insert_newline_at_middle() {
         Insert(0, "123456", 0),
         Link(0, Interval::new(0, 6), "https://appflowy.io"),
         Insert(0, NEW_LINE, 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"},{"insert":"456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
         ),
@@ -549,9 +552,9 @@ fn attributes_link_auto_format() {
     let site = "https://appflowy.io";
     let ops = vec![
         Insert(0, site, 0),
-        AssertOpsJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
         Insert(0, WHITESPACE, site.len()),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#,
         ),
@@ -567,7 +570,7 @@ fn attributes_link_auto_format_exist() {
         Insert(0, site, 0),
         Link(0, Interval::new(0, site.len()), site),
         Insert(0, WHITESPACE, site.len()),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#,
         ),
@@ -583,7 +586,7 @@ fn attributes_link_auto_format_exist2() {
         Insert(0, site, 0),
         Link(0, Interval::new(0, site.len() / 2), site),
         Insert(0, WHITESPACE, site.len()),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"https://a","attributes":{"link":"https://appflowy.io"}},{"insert":"ppflowy.io \n"}]"#,
         ),
@@ -597,7 +600,7 @@ fn attributes_bullet_added() {
     let ops = vec![
         Insert(0, "12", 0),
         Bullet(0, Interval::new(0, 1), true),
-        AssertOpsJson(0, r#"[{"insert":"12"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"12"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
     ];
 
     TestBuilder::new().run_script::<FlowyDoc>(ops);
@@ -608,11 +611,14 @@ fn attributes_bullet_added_2() {
     let ops = vec![
         Insert(0, "1", 0),
         Bullet(0, Interval::new(0, 1), true),
-        AssertOpsJson(0, r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
         Insert(0, NEW_LINE, 1),
-        AssertOpsJson(0, r#"[{"insert":"1"},{"insert":"\n\n","attributes":{"list":"bullet"}}]"#),
+        AssertDocJson(
+            0,
+            r#"[{"insert":"1"},{"insert":"\n\n","attributes":{"list":"bullet"}}]"#,
+        ),
         Insert(0, "2", 2),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
         ),
@@ -629,7 +635,7 @@ fn attributes_bullet_remove_partial() {
         Insert(0, NEW_LINE, 1),
         Insert(0, "2", 2),
         Bullet(0, Interval::new(2, 3), false),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2\n"}]"#,
         ),
@@ -645,7 +651,7 @@ fn attributes_bullet_auto_exit() {
         Bullet(0, Interval::new(0, 1), true),
         Insert(0, NEW_LINE, 1),
         Insert(0, NEW_LINE, 2),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"\n"}]"#,
         ),
@@ -660,9 +666,12 @@ fn attributes_preserve_block_when_insert_newline_inside() {
         Insert(0, "12", 0),
         Bullet(0, Interval::new(0, 2), true),
         Insert(0, NEW_LINE, 2),
-        AssertOpsJson(0, r#"[{"insert":"12"},{"insert":"\n\n","attributes":{"list":"bullet"}}]"#),
+        AssertDocJson(
+            0,
+            r#"[{"insert":"12"},{"insert":"\n\n","attributes":{"list":"bullet"}}]"#,
+        ),
         Insert(0, "34", 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12"},{"insert":"\n","attributes":{"list":"bullet"}},
@@ -670,7 +679,7 @@ fn attributes_preserve_block_when_insert_newline_inside() {
             ]"#,
         ),
         Insert(0, NEW_LINE, 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12"},{"insert":"\n\n","attributes":{"list":"bullet"}},
@@ -678,7 +687,7 @@ fn attributes_preserve_block_when_insert_newline_inside() {
             ]"#,
         ),
         Insert(0, "ab", 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12"},{"insert":"\n","attributes":{"list":"bullet"}},
@@ -697,12 +706,12 @@ fn attributes_preserve_header_format_on_merge() {
         Insert(0, "123456", 0),
         Header(0, Interval::new(0, 6), 1),
         Insert(0, NEW_LINE, 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123"},{"insert":"\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#,
         ),
         Delete(0, Interval::new(3, 4)),
-        AssertOpsJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}}]"#),
     ];
 
     TestBuilder::new().run_script::<FlowyDoc>(ops);
@@ -714,12 +723,15 @@ fn attributes_preserve_list_format_on_merge() {
         Insert(0, "123456", 0),
         Bullet(0, Interval::new(0, 6), true),
         Insert(0, NEW_LINE, 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
         ),
         Delete(0, Interval::new(3, 4)),
-        AssertOpsJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
+        AssertDocJson(
+            0,
+            r#"[{"insert":"123456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
+        ),
     ];
 
     TestBuilder::new().run_script::<FlowyDoc>(ops);

+ 78 - 15
rust-lib/flowy-document/tests/editor/mod.rs

@@ -45,6 +45,9 @@ pub enum TestOp {
     #[display(fmt = "Transform")]
     Transform(usize, usize),
 
+    #[display(fmt = "TransformPrime")]
+    TransformPrime(usize, usize),
+
     // invert the delta_a base on the delta_b
     #[display(fmt = "Invert")]
     Invert(usize, usize),
@@ -61,12 +64,23 @@ pub enum TestOp {
     #[display(fmt = "AssertStr")]
     AssertStr(usize, &'static str),
 
-    #[display(fmt = "AssertOpsJson")]
-    AssertOpsJson(usize, &'static str),
+    #[display(fmt = "AssertDocJson")]
+    AssertDocJson(usize, &'static str),
+
+    #[display(fmt = "AssertPrimeJson")]
+    AssertPrimeJson(usize, &'static str),
+
+    #[display(fmt = "DocComposeDelta")]
+    DocComposeDelta(usize, usize),
+
+    #[display(fmt = "ApplyPrimeDelta")]
+    DocComposePrime(usize, usize),
 }
 
 pub struct TestBuilder {
     documents: Vec<Document>,
+    deltas: Vec<Option<Delta>>,
+    primes: Vec<Option<Delta>>,
 }
 
 impl TestBuilder {
@@ -78,7 +92,11 @@ impl TestBuilder {
             env_logger::init();
         });
 
-        Self { documents: vec![] }
+        Self {
+            documents: vec![],
+            deltas: vec![],
+            primes: vec![],
+        }
     }
 
     fn run_op(&mut self, op: &TestOp) {
@@ -86,15 +104,18 @@ impl TestBuilder {
         match op {
             TestOp::Insert(delta_i, s, index) => {
                 let document = &mut self.documents[*delta_i];
-                document.insert(*index, s).unwrap();
+                let delta = document.insert(*index, s).unwrap();
+                self.deltas.insert(*delta_i, Some(delta));
             },
             TestOp::Delete(delta_i, iv) => {
                 let document = &mut self.documents[*delta_i];
-                document.replace(*iv, "").unwrap();
+                let delta = document.replace(*iv, "").unwrap();
+                self.deltas.insert(*delta_i, Some(delta));
             },
             TestOp::Replace(delta_i, iv, s) => {
                 let document = &mut self.documents[*delta_i];
-                document.replace(*iv, s).unwrap();
+                let delta = document.replace(*iv, s).unwrap();
+                self.deltas.insert(*delta_i, Some(delta));
             },
             TestOp::InsertBold(delta_i, s, iv) => {
                 let document = &mut self.documents[*delta_i];
@@ -104,7 +125,8 @@ impl TestBuilder {
             TestOp::Bold(delta_i, iv, enable) => {
                 let document = &mut self.documents[*delta_i];
                 let attribute = Attribute::Bold(*enable);
-                document.format(*iv, attribute).unwrap();
+                let delta = document.format(*iv, attribute).unwrap();
+                self.deltas.insert(*delta_i, Some(delta));
             },
             TestOp::Italic(delta_i, iv, enable) => {
                 let document = &mut self.documents[*delta_i];
@@ -112,22 +134,26 @@ impl TestBuilder {
                     true => Attribute::Italic(true),
                     false => Attribute::Italic(false),
                 };
-                document.format(*iv, attribute).unwrap();
+                let delta = document.format(*iv, attribute).unwrap();
+                self.deltas.insert(*delta_i, Some(delta));
             },
             TestOp::Header(delta_i, iv, level) => {
                 let document = &mut self.documents[*delta_i];
                 let attribute = Attribute::Header(*level);
-                document.format(*iv, attribute).unwrap();
+                let delta = document.format(*iv, attribute).unwrap();
+                self.deltas.insert(*delta_i, Some(delta));
             },
             TestOp::Link(delta_i, iv, link) => {
                 let document = &mut self.documents[*delta_i];
                 let attribute = Attribute::Link(link.to_owned());
-                document.format(*iv, attribute).unwrap();
+                let delta = document.format(*iv, attribute).unwrap();
+                self.deltas.insert(*delta_i, Some(delta));
             },
             TestOp::Bullet(delta_i, iv, enable) => {
                 let document = &mut self.documents[*delta_i];
                 let attribute = Attribute::Bullet(*enable);
-                document.format(*iv, attribute).unwrap();
+                let delta = document.format(*iv, attribute).unwrap();
+                self.deltas.insert(*delta_i, Some(delta));
             },
             TestOp::Transform(delta_a_i, delta_b_i) => {
                 let (a_prime, b_prime) = self.documents[*delta_a_i]
@@ -142,6 +168,15 @@ impl TestBuilder {
                 self.documents[*delta_a_i].set_delta(data_left);
                 self.documents[*delta_b_i].set_delta(data_right);
             },
+            TestOp::TransformPrime(a_doc_index, b_doc_index) => {
+                let (prime_left, prime_right) = self.documents[*a_doc_index]
+                    .delta()
+                    .transform(&self.documents[*b_doc_index].delta())
+                    .unwrap();
+
+                self.primes.insert(*a_doc_index, Some(prime_left));
+                self.primes.insert(*b_doc_index, Some(prime_right));
+            },
             TestOp::Invert(delta_a_i, delta_b_i) => {
                 let delta_a = &self.documents[*delta_a_i].delta();
                 let delta_b = &self.documents[*delta_b_i].delta();
@@ -177,22 +212,50 @@ impl TestBuilder {
                 assert_eq!(&self.documents[*delta_i].to_plain_string(), expected);
             },
 
-            TestOp::AssertOpsJson(delta_i, expected) => {
-                let delta_i_json = self.documents[*delta_i].to_json();
+            TestOp::AssertDocJson(delta_i, expected) => {
+                let delta_json = self.documents[*delta_i].to_json();
                 let expected_delta: Delta = serde_json::from_str(expected).unwrap();
-                let target_delta: Delta = serde_json::from_str(&delta_i_json).unwrap();
+                let target_delta: Delta = serde_json::from_str(&delta_json).unwrap();
 
                 if expected_delta != target_delta {
                     log::error!("✅ expect: {}", expected,);
-                    log::error!("❌ receive: {}", delta_i_json);
+                    log::error!("❌ receive: {}", delta_json);
                 }
                 assert_eq!(target_delta, expected_delta);
             },
+
+            TestOp::AssertPrimeJson(doc_i, expected) => {
+                let prime_json = self.primes[*doc_i].as_ref().unwrap().to_json();
+                let expected_prime: Delta = serde_json::from_str(expected).unwrap();
+                let target_prime: Delta = serde_json::from_str(&prime_json).unwrap();
+
+                if expected_prime != target_prime {
+                    log::error!("✅ expect prime: {}", expected,);
+                    log::error!("❌ receive prime: {}", prime_json);
+                }
+                assert_eq!(target_prime, expected_prime);
+            },
+            TestOp::DocComposeDelta(doc_index, delta_i) => {
+                let delta = self.deltas.get(*delta_i).unwrap().as_ref().unwrap();
+                self.documents[*doc_index].compose_delta(delta);
+            },
+            TestOp::DocComposePrime(doc_index, prime_i) => {
+                let delta = self
+                    .primes
+                    .get(*prime_i)
+                    .expect("Must call TransformPrime first")
+                    .as_ref()
+                    .unwrap();
+                let new_delta = self.documents[*doc_index].delta().compose(delta).unwrap();
+                self.documents[*doc_index].set_delta(new_delta);
+            },
         }
     }
 
     pub fn run_script<C: CustomDocument>(mut self, script: Vec<TestOp>) {
         self.documents = vec![Document::new::<C>(), Document::new::<C>()];
+        self.primes = vec![None, None];
+        self.deltas = vec![None, None];
         for (_i, op) in script.iter().enumerate() {
             self.run_op(op);
         }

+ 96 - 39
rust-lib/flowy-document/tests/editor/op_test.rs

@@ -8,7 +8,7 @@ fn attributes_insert_text() {
     let ops = vec![
         Insert(0, "123", 0),
         Insert(0, "456", 3),
-        AssertOpsJson(0, r#"[{"insert":"123456"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -18,7 +18,7 @@ fn attributes_insert_text_at_head() {
     let ops = vec![
         Insert(0, "123", 0),
         Insert(0, "456", 0),
-        AssertOpsJson(0, r#"[{"insert":"456123"}]"#),
+        AssertDocJson(0, r#"[{"insert":"456123"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -28,7 +28,7 @@ fn attributes_insert_text_at_middle() {
     let ops = vec![
         Insert(0, "123", 0),
         Insert(0, "456", 1),
-        AssertOpsJson(0, r#"[{"insert":"145623"}]"#),
+        AssertDocJson(0, r#"[{"insert":"145623"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -69,7 +69,10 @@ fn delta_get_ops_in_interval_2() {
         vec![OpBuilder::insert("23").build()]
     );
 
-    assert_eq!(DeltaIter::from_interval(&delta, Interval::new(0, 3)).ops(), vec![insert_a.clone()]);
+    assert_eq!(
+        DeltaIter::from_interval(&delta, Interval::new(0, 3)).ops(),
+        vec![insert_a.clone()]
+    );
 
     assert_eq!(
         DeltaIter::from_interval(&delta, Interval::new(0, 4)).ops(),
@@ -109,9 +112,18 @@ fn delta_get_ops_in_interval_4() {
     delta.ops.push(insert_b.clone());
     delta.ops.push(insert_c.clone());
 
-    assert_eq!(DeltaIter::from_interval(&delta, Interval::new(0, 2)).ops(), vec![insert_a]);
-    assert_eq!(DeltaIter::from_interval(&delta, Interval::new(2, 4)).ops(), vec![insert_b]);
-    assert_eq!(DeltaIter::from_interval(&delta, Interval::new(4, 6)).ops(), vec![insert_c]);
+    assert_eq!(
+        DeltaIter::from_interval(&delta, Interval::new(0, 2)).ops(),
+        vec![insert_a]
+    );
+    assert_eq!(
+        DeltaIter::from_interval(&delta, Interval::new(2, 4)).ops(),
+        vec![insert_b]
+    );
+    assert_eq!(
+        DeltaIter::from_interval(&delta, Interval::new(4, 6)).ops(),
+        vec![insert_c]
+    );
 
     assert_eq!(
         DeltaIter::from_interval(&delta, Interval::new(2, 5)).ops(),
@@ -443,7 +455,7 @@ fn compose() {
     }
 }
 #[test]
-fn transform() {
+fn transform_random_delta() {
     for _ in 0..1000 {
         let mut rng = Rng::default();
         let s = rng.gen_string(20);
@@ -461,19 +473,7 @@ fn transform() {
 }
 
 #[test]
-fn transform2() {
-    let ops = vec![
-        Insert(0, "123", 0),
-        Insert(1, "456", 0),
-        Transform(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"123456"}]"#),
-        AssertOpsJson(1, r#"[{"insert":"123456"}]"#),
-    ];
-    TestBuilder::new().run_script::<PlainDoc>(ops);
-}
-
-#[test]
-fn delta_transform_test() {
+fn transform_with_two_delta_test() {
     let mut a = Delta::default();
     let mut a_s = String::new();
     a.insert("123", AttributeBuilder::new().add(Attribute::Bold(true)).build());
@@ -509,6 +509,65 @@ fn delta_transform_test() {
     );
 }
 
+#[test]
+fn transform_two_plain_delta_test() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(1, "456", 0),
+        Transform(0, 1),
+        AssertDocJson(0, r#"[{"insert":"123456"}]"#),
+        AssertDocJson(1, r#"[{"insert":"123456"}]"#),
+    ];
+    TestBuilder::new().run_script::<PlainDoc>(ops);
+}
+
+#[test]
+fn transform_two_plain_delta_test2() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(1, "456", 0),
+        TransformPrime(0, 1),
+        DocComposePrime(0, 1),
+        DocComposePrime(1, 0),
+        AssertDocJson(0, r#"[{"insert":"123456"}]"#),
+        AssertDocJson(1, r#"[{"insert":"123456"}]"#),
+    ];
+    TestBuilder::new().run_script::<PlainDoc>(ops);
+}
+
+#[test]
+fn transform_two_non_seq_delta() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(1, "456", 0),
+        TransformPrime(0, 1),
+        AssertPrimeJson(0, r#"[{"insert":"123"},{"retain":3}]"#),
+        AssertPrimeJson(1, r#"[{"retain":3},{"insert":"456"}]"#),
+        DocComposePrime(0, 1),
+        Insert(1, "78", 3),
+        Insert(1, "9", 5),
+        DocComposePrime(1, 0),
+        AssertDocJson(0, r#"[{"insert":"123456"}]"#),
+        AssertDocJson(1, r#"[{"insert":"123456789"}]"#),
+    ];
+    TestBuilder::new().run_script::<PlainDoc>(ops);
+}
+
+#[test]
+fn transform_two_conflict_non_seq_delta() {
+    let ops = vec![
+        Insert(0, "123", 0),
+        Insert(1, "456", 0),
+        TransformPrime(0, 1),
+        DocComposePrime(0, 1),
+        Insert(1, "78", 0),
+        DocComposePrime(1, 0),
+        AssertDocJson(0, r#"[{"insert":"123456"}]"#),
+        AssertDocJson(1, r#"[{"insert":"12378456"}]"#),
+    ];
+    TestBuilder::new().run_script::<PlainDoc>(ops);
+}
+
 #[test]
 fn delta_invert_no_attribute_delta() {
     let mut delta = Delta::default();
@@ -531,7 +590,7 @@ fn delta_invert_no_attribute_delta2() {
         Insert(0, "123", 0),
         Insert(1, "4567", 0),
         Invert(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -541,10 +600,10 @@ fn delta_invert_attribute_delta_with_no_attribute_delta() {
     let ops = vec![
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
-        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
         Insert(1, "4567", 0),
         Invert(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -555,14 +614,14 @@ fn delta_invert_attribute_delta_with_no_attribute_delta2() {
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
         Insert(0, "456", 3),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"123456","attributes":{"bold":"true"}}]
             "#,
         ),
         Italic(0, Interval::new(2, 4), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"bold":"true"}}, 
@@ -572,7 +631,7 @@ fn delta_invert_attribute_delta_with_no_attribute_delta2() {
         ),
         Insert(1, "abc", 0),
         Invert(0, 1),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"bold":"true"}},
@@ -590,9 +649,9 @@ fn delta_invert_no_attribute_delta_with_attribute_delta() {
         Insert(0, "123", 0),
         Insert(1, "4567", 0),
         Bold(1, Interval::new(0, 3), true),
-        AssertOpsJson(1, r#"[{"insert":"456","attributes":{"bold":"true"}},{"insert":"7"}]"#),
+        AssertDocJson(1, r#"[{"insert":"456","attributes":{"bold":"true"}},{"insert":"7"}]"#),
         Invert(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -601,19 +660,17 @@ fn delta_invert_no_attribute_delta_with_attribute_delta() {
 fn delta_invert_no_attribute_delta_with_attribute_delta2() {
     let ops = vec![
         Insert(0, "123", 0),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123"}]"#),
         Insert(1, "abc", 0),
         Bold(1, Interval::new(0, 3), true),
         Insert(1, "d", 3),
         Italic(1, Interval::new(1, 3), true),
-        AssertOpsJson(
+        AssertDocJson(
             1,
-            r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":
-{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"
-}}]"#,
+            r#"[{"insert":"a","attributes":{"bold":"true"}},{"insert":"bc","attributes":{"bold":"true","italic":"true"}},{"insert":"d","attributes":{"bold":"true"}}]"#,
         ),
         Invert(0, 1),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -624,9 +681,9 @@ fn delta_invert_attribute_delta_with_attribute_delta() {
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
         Insert(0, "456", 3),
-        AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#),
         Italic(0, Interval::new(2, 4), true),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"bold":"true"}},
@@ -638,7 +695,7 @@ fn delta_invert_attribute_delta_with_attribute_delta() {
         Bold(1, Interval::new(0, 3), true),
         Insert(1, "d", 3),
         Italic(1, Interval::new(1, 3), true),
-        AssertOpsJson(
+        AssertDocJson(
             1,
             r#"[
             {"insert":"a","attributes":{"bold":"true"}},
@@ -647,7 +704,7 @@ fn delta_invert_attribute_delta_with_attribute_delta() {
             ]"#,
         ),
         Invert(0, 1),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"12","attributes":{"bold":"true"}},

+ 50 - 47
rust-lib/flowy-document/tests/editor/undo_redo_test.rs

@@ -4,7 +4,7 @@ use flowy_ot::core::{Interval, NEW_LINE, WHITESPACE};
 
 #[test]
 fn history_insert_undo() {
-    let ops = vec![Insert(0, "123", 0), Undo(0), AssertOpsJson(0, r#"[{"insert":"\n"}]"#)];
+    let ops = vec![Insert(0, "123", 0), Undo(0), AssertDocJson(0, r#"[{"insert":"\n"}]"#)];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
 
@@ -15,9 +15,9 @@ fn history_insert_undo_with_lagging() {
         Wait(RECORD_THRESHOLD),
         Insert(0, "456", 0),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -26,11 +26,11 @@ fn history_insert_undo_with_lagging() {
 fn history_insert_redo() {
     let ops = vec![
         Insert(0, "123", 0),
-        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
         Redo(0),
-        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -43,13 +43,13 @@ fn history_insert_redo_with_lagging() {
         Insert(0, "456", 3),
         Wait(RECORD_THRESHOLD),
         AssertStr(0, "123456\n"),
-        AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456\n"}]"#),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
         Redo(0),
-        AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123456\n"}]"#),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -60,7 +60,7 @@ fn history_bold_undo() {
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -72,7 +72,7 @@ fn history_bold_undo_with_lagging() {
         Wait(RECORD_THRESHOLD),
         Bold(0, Interval::new(0, 3), true),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -83,9 +83,9 @@ fn history_bold_redo() {
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
         Redo(0),
-        AssertOpsJson(0, r#" [{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
+        AssertDocJson(0, r#" [{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -97,9 +97,9 @@ fn history_bold_redo_with_lagging() {
         Wait(RECORD_THRESHOLD),
         Bold(0, Interval::new(0, 3), true),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
         Redo(0),
-        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -108,11 +108,11 @@ fn history_bold_redo_with_lagging() {
 fn history_delete_undo() {
     let ops = vec![
         Insert(0, "123", 0),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123"}]"#),
         Delete(0, Interval::new(0, 3)),
-        AssertOpsJson(0, r#"[]"#),
+        AssertDocJson(0, r#"[]"#),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"123"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123"}]"#),
     ];
     TestBuilder::new().run_script::<PlainDoc>(ops);
 }
@@ -123,7 +123,7 @@ fn history_delete_undo_2() {
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
         Delete(0, Interval::new(0, 1)),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"23","attributes":{"bold":"true"}},
@@ -131,7 +131,7 @@ fn history_delete_undo_2() {
             "#,
         ),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -144,7 +144,7 @@ fn history_delete_undo_with_lagging() {
         Bold(0, Interval::new(0, 3), true),
         Wait(RECORD_THRESHOLD),
         Delete(0, Interval::new(0, 1)),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"23","attributes":{"bold":"true"}},
@@ -152,7 +152,7 @@ fn history_delete_undo_with_lagging() {
             "#,
         ),
         Undo(0),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"123","attributes":{"bold":"true"}},
@@ -169,10 +169,10 @@ fn history_delete_redo() {
         Insert(0, "123", 0),
         Wait(RECORD_THRESHOLD),
         Delete(0, Interval::new(0, 3)),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
         Undo(0),
         Redo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -183,7 +183,7 @@ fn history_replace_undo() {
         Insert(0, "123", 0),
         Bold(0, Interval::new(0, 3), true),
         Replace(0, Interval::new(0, 2), "ab"),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"ab"},
@@ -191,7 +191,7 @@ fn history_replace_undo() {
             "#,
         ),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -204,7 +204,7 @@ fn history_replace_undo_with_lagging() {
         Bold(0, Interval::new(0, 3), true),
         Wait(RECORD_THRESHOLD),
         Replace(0, Interval::new(0, 2), "ab"),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"ab"},
@@ -212,7 +212,7 @@ fn history_replace_undo_with_lagging() {
             "#,
         ),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
     ];
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
@@ -225,7 +225,7 @@ fn history_replace_redo() {
         Replace(0, Interval::new(0, 2), "ab"),
         Undo(0),
         Redo(0),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[
             {"insert":"ab"},
@@ -244,9 +244,9 @@ fn history_header_added_undo() {
         Insert(0, "\n", 3),
         Insert(0, "\n", 4),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
         Redo(0),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#,
         ),
@@ -263,9 +263,9 @@ fn history_link_added_undo() {
         Wait(RECORD_THRESHOLD),
         Link(0, Interval::new(0, site.len()), site),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
         Redo(0),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
         ),
@@ -279,15 +279,15 @@ fn history_link_auto_format_undo_with_lagging() {
     let site = "https://appflowy.io";
     let ops = vec![
         Insert(0, site, 0),
-        AssertOpsJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
         Wait(RECORD_THRESHOLD),
         Insert(0, WHITESPACE, site.len()),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#,
         ),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
     ];
 
     TestBuilder::new().run_script::<FlowyDoc>(ops);
@@ -300,14 +300,14 @@ fn history_bullet_undo() {
         Bullet(0, Interval::new(0, 1), true),
         Insert(0, NEW_LINE, 1),
         Insert(0, "2", 2),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
         ),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
         Redo(0),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
         ),
@@ -325,17 +325,17 @@ fn history_bullet_undo_with_lagging() {
         Insert(0, NEW_LINE, 1),
         Insert(0, "2", 2),
         Wait(RECORD_THRESHOLD),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
         ),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
+        AssertDocJson(0, r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
         Undo(0),
-        AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
         Redo(0),
         Redo(0),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
         ),
@@ -352,14 +352,17 @@ fn history_undo_attribute_on_merge_between_line() {
         Wait(RECORD_THRESHOLD),
         Insert(0, NEW_LINE, 3),
         Wait(RECORD_THRESHOLD),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
         ),
         Delete(0, Interval::new(3, 4)), // delete the newline
-        AssertOpsJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
+        AssertDocJson(
+            0,
+            r#"[{"insert":"123456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
+        ),
         Undo(0),
-        AssertOpsJson(
+        AssertDocJson(
             0,
             r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#,
         ),

+ 9 - 8
rust-lib/flowy-ot/src/core/delta/delta.rs

@@ -48,10 +48,7 @@ impl std::convert::TryFrom<Vec<u8>> for Delta {
 impl std::convert::TryFrom<Bytes> for Delta {
     type Error = OTError;
 
-    fn try_from(value: Bytes) -> Result<Self, Self::Error> {
-        let bytes = value.to_vec();
-        Delta::from_bytes(bytes)
-    }
+    fn try_from(bytes: Bytes) -> Result<Self, Self::Error> { Delta::from_bytes(&bytes) }
 }
 
 // impl<T: AsRef<Vec<u8>>> std::convert::From<T> for Delta {
@@ -94,8 +91,8 @@ impl Delta {
 
     pub fn to_json(&self) -> String { serde_json::to_string(self).unwrap_or("".to_owned()) }
 
-    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, OTError> {
-        let json = str::from_utf8(&bytes)?;
+    pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, OTError> {
+        let json = str::from_utf8(bytes.as_ref())?;
         Self::from_json(json)
     }
 
@@ -271,8 +268,12 @@ impl OperationTransformable for Delta {
                 other_iter.next_op_len().unwrap_or(MAX_IV_LEN),
             );
 
-            let op = iter.next_op_with_len(length).unwrap_or(OpBuilder::retain(length).build());
-            let other_op = other_iter.next_op_with_len(length).unwrap_or(OpBuilder::retain(length).build());
+            let op = iter
+                .next_op_with_len(length)
+                .unwrap_or(OpBuilder::retain(length).build());
+            let other_op = other_iter
+                .next_op_with_len(length)
+                .unwrap_or(OpBuilder::retain(length).build());
 
             debug_assert_eq!(op.len(), other_op.len());
 

+ 17 - 6
rust-lib/flowy-workspace/src/entities/view/view_create.rs

@@ -65,10 +65,11 @@ pub struct CreateViewParams {
     pub view_type: ViewType,
 
     #[pb(index = 6)]
-    pub data: Vec<u8>,
+    pub data: String,
 }
 
-const VIEW_DEFAULT_DATA: &str = "[{\"insert\":\"\\n\"}]";
+pub const VIEW_DEFAULT_DATA: &str = "[{\"insert\":\"\\n\"}]";
+#[allow(dead_code)]
 pub fn default_delta() -> Vec<u8> { VIEW_DEFAULT_DATA.as_bytes().to_vec() }
 
 impl CreateViewParams {
@@ -79,7 +80,7 @@ impl CreateViewParams {
             desc,
             thumbnail,
             view_type,
-            data: default_delta(),
+            data: VIEW_DEFAULT_DATA.to_string(),
         }
     }
 }
@@ -88,9 +89,13 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
     type Error = WorkspaceError;
 
     fn try_into(self) -> Result<CreateViewParams, Self::Error> {
-        let name = ViewName::parse(self.name).map_err(|e| WorkspaceError::view_name().context(e))?.0;
+        let name = ViewName::parse(self.name)
+            .map_err(|e| WorkspaceError::view_name().context(e))?
+            .0;
 
-        let belong_to_id = AppId::parse(self.belong_to_id).map_err(|e| WorkspaceError::app_id().context(e))?.0;
+        let belong_to_id = AppId::parse(self.belong_to_id)
+            .map_err(|e| WorkspaceError::app_id().context(e))?
+            .0;
 
         let thumbnail = match self.thumbnail {
             None => "".to_string(),
@@ -101,7 +106,13 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
             },
         };
 
-        Ok(CreateViewParams::new(belong_to_id, name, self.desc, self.view_type, thumbnail))
+        Ok(CreateViewParams::new(
+            belong_to_id,
+            name,
+            self.desc,
+            self.view_type,
+            thumbnail,
+        ))
     }
 }
 

+ 34 - 26
rust-lib/flowy-workspace/src/entities/view/view_update.rs

@@ -3,7 +3,7 @@ use crate::{
     errors::WorkspaceError,
 };
 use flowy_derive::ProtoBuf;
-use flowy_document::entities::doc::DocDelta;
+
 use std::convert::TryInto;
 
 #[derive(Default, ProtoBuf)]
@@ -69,16 +69,26 @@ impl TryInto<UpdateViewParams> for UpdateViewRequest {
     type Error = WorkspaceError;
 
     fn try_into(self) -> Result<UpdateViewParams, Self::Error> {
-        let view_id = ViewId::parse(self.view_id).map_err(|e| WorkspaceError::view_id().context(e))?.0;
+        let view_id = ViewId::parse(self.view_id)
+            .map_err(|e| WorkspaceError::view_id().context(e))?
+            .0;
 
         let name = match self.name {
             None => None,
-            Some(name) => Some(ViewName::parse(name).map_err(|e| WorkspaceError::view_name().context(e))?.0),
+            Some(name) => Some(
+                ViewName::parse(name)
+                    .map_err(|e| WorkspaceError::view_name().context(e))?
+                    .0,
+            ),
         };
 
         let desc = match self.desc {
             None => None,
-            Some(desc) => Some(ViewDesc::parse(desc).map_err(|e| WorkspaceError::view_desc().context(e))?.0),
+            Some(desc) => Some(
+                ViewDesc::parse(desc)
+                    .map_err(|e| WorkspaceError::view_desc().context(e))?
+                    .0,
+            ),
         };
 
         let thumbnail = match self.thumbnail {
@@ -99,25 +109,23 @@ impl TryInto<UpdateViewParams> for UpdateViewRequest {
         })
     }
 }
-
-#[derive(Default, ProtoBuf)]
-pub struct ApplyChangesetRequest {
-    #[pb(index = 1)]
-    pub view_id: String,
-
-    #[pb(index = 2)]
-    pub data: Vec<u8>,
-}
-
-impl TryInto<DocDelta> for ApplyChangesetRequest {
-    type Error = WorkspaceError;
-
-    fn try_into(self) -> Result<DocDelta, Self::Error> {
-        let view_id = ViewId::parse(self.view_id).map_err(|e| WorkspaceError::view_id().context(e))?.0;
-
-        // Opti: Vec<u8> -> Delta -> Vec<u8>
-        let data = DeltaData::parse(self.data).map_err(|e| WorkspaceError::view_data().context(e))?.0;
-
-        Ok(DocDelta { doc_id: view_id, data })
-    }
-}
+// #[derive(Default, ProtoBuf)]
+// pub struct DocDeltaRequest {
+//     #[pb(index = 1)]
+//     pub view_id: String,
+//
+//     #[pb(index = 2)]
+//     pub data: String,
+// }
+//
+// impl TryInto<DocDelta> for DocDeltaRequest {
+//     type Error = WorkspaceError;
+//
+//     fn try_into(self) -> Result<DocDelta, Self::Error> {
+//         let view_id = ViewId::parse(self.view_id)
+//             .map_err(|e| WorkspaceError::view_id().context(e))?
+//             .0;
+//
+//         Ok(DocDelta { doc_id: view_id, data: self.data })
+//     }
+// }

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

@@ -1,6 +1,5 @@
 use crate::{
     entities::view::{
-        ApplyChangesetRequest,
         CreateViewParams,
         CreateViewRequest,
         DeleteViewParams,
@@ -57,11 +56,11 @@ pub(crate) async fn update_view_handler(
 
 #[tracing::instrument(skip(data, controller), err)]
 pub(crate) async fn apply_doc_delta_handler(
-    data: Data<ApplyChangesetRequest>,
+    data: Data<DocDelta>,
     controller: Unit<Arc<ViewController>>,
 ) -> DataResult<Doc, WorkspaceError> {
-    let params: DocDelta = data.into_inner().try_into()?;
-    let doc = controller.apply_doc_delta(params).await?;
+    // let params: DocDelta = data.into_inner().try_into()?;
+    let doc = controller.apply_doc_delta(data.into_inner()).await?;
     data_result(doc)
 }
 

+ 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::vec::Vec<u8>,
+    pub data: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -523,10 +523,10 @@ impl CreateViewParams {
         self.view_type = v;
     }
 
-    // bytes data = 6;
+    // string data = 6;
 
 
-    pub fn get_data(&self) -> &[u8] {
+    pub fn get_data(&self) -> &str {
         &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::vec::Vec<u8>) {
+    pub fn set_data(&mut self, v: ::std::string::String) {
         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> {
+    pub fn mut_data(&mut self) -> &mut ::std::string::String {
         &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())
+    pub fn take_data(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.data, ::std::string::String::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_bytes_into(wire_type, is, &mut self.data)?;
+                    ::protobuf::rt::read_singular_proto3_string_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::bytes_size(6, &self.data);
+            my_size += ::protobuf::rt::string_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_bytes(6, &self.data)?;
+            os.write_string(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::ProtobufTypeBytes>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "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(\x0cR\x04data\"\x97\x02\n\x04View\
+    Type\x12\x12\n\x04data\x18\x06\x20\x01(\tR\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\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\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\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\

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

@@ -944,7 +944,7 @@ impl ::protobuf::reflect::ProtobufValue for UpdateViewParams {
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct ApplyChangesetRequest {
+pub struct DocDeltaRequest {
     // message fields
     pub view_id: ::std::string::String,
     pub data: ::std::vec::Vec<u8>,
@@ -953,14 +953,14 @@ pub struct ApplyChangesetRequest {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a ApplyChangesetRequest {
-    fn default() -> &'a ApplyChangesetRequest {
-        <ApplyChangesetRequest as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a DocDeltaRequest {
+    fn default() -> &'a DocDeltaRequest {
+        <DocDeltaRequest as ::protobuf::Message>::default_instance()
     }
 }
 
-impl ApplyChangesetRequest {
-    pub fn new() -> ApplyChangesetRequest {
+impl DocDeltaRequest {
+    pub fn new() -> DocDeltaRequest {
         ::std::default::Default::default()
     }
 
@@ -1017,7 +1017,7 @@ impl ApplyChangesetRequest {
     }
 }
 
-impl ::protobuf::Message for ApplyChangesetRequest {
+impl ::protobuf::Message for DocDeltaRequest {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -1092,8 +1092,8 @@ impl ::protobuf::Message for ApplyChangesetRequest {
         Self::descriptor_static()
     }
 
-    fn new() -> ApplyChangesetRequest {
-        ApplyChangesetRequest::new()
+    fn new() -> DocDeltaRequest {
+        DocDeltaRequest::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -1102,29 +1102,29 @@ impl ::protobuf::Message for ApplyChangesetRequest {
             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 },
+                |m: &DocDeltaRequest| { &m.view_id },
+                |m: &mut DocDeltaRequest| { &mut m.view_id },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
                 "data",
-                |m: &ApplyChangesetRequest| { &m.data },
-                |m: &mut ApplyChangesetRequest| { &mut m.data },
+                |m: &DocDeltaRequest| { &m.data },
+                |m: &mut DocDeltaRequest| { &mut m.data },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<ApplyChangesetRequest>(
-                "ApplyChangesetRequest",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DocDeltaRequest>(
+                "DocDeltaRequest",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static ApplyChangesetRequest {
-        static instance: ::protobuf::rt::LazyV2<ApplyChangesetRequest> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(ApplyChangesetRequest::new)
+    fn default_instance() -> &'static DocDeltaRequest {
+        static instance: ::protobuf::rt::LazyV2<DocDeltaRequest> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(DocDeltaRequest::new)
     }
 }
 
-impl ::protobuf::Clear for ApplyChangesetRequest {
+impl ::protobuf::Clear for DocDeltaRequest {
     fn clear(&mut self) {
         self.view_id.clear();
         self.data.clear();
@@ -1132,13 +1132,13 @@ impl ::protobuf::Clear for ApplyChangesetRequest {
     }
 }
 
-impl ::std::fmt::Debug for ApplyChangesetRequest {
+impl ::std::fmt::Debug for DocDeltaRequest {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for ApplyChangesetRequest {
+impl ::protobuf::reflect::ProtobufValue for DocDeltaRequest {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -1156,55 +1156,55 @@ 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\x15ApplyChangesetRequest\x12\x17\n\x07vie\
-    w_id\x18\x01\x20\x01(\tR\x06viewId\x12\x12\n\x04data\x18\x02\x20\x01(\
-    \x0cR\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\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\x12b\x06proto3\
+    \x11\n\x0fone_of_is_trash\">\n\x0fDocDeltaRequest\x12\x17\n\x07view_id\
+    \x18\x01\x20\x01(\tR\x06viewId\x12\x12\n\x04data\x18\x02\x20\x01(\x0cR\
+    \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\x17\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\x12b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 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;
-    bytes data = 6;
+    string data = 6;
 }
 message View {
     string id = 1;

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

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

+ 1 - 1
rust-lib/rustfmt.toml

@@ -1,5 +1,5 @@
 # https://rust-lang.github.io/rustfmt/?version=master&search=
-max_width = 140
+max_width = 120
 tab_spaces = 4
 fn_single_line = true
 match_block_trailing_comma = true