From fa2cfd7c208b748ad84c48c81a73b19ef8cafd7b Mon Sep 17 00:00:00 2001
From: appflowy <annie@appflowy.io>
Date: Tue, 13 Sep 2022 20:23:56 +0800
Subject: [PATCH] chore: node transform path test

---
 .../lib/plugins/doc/application/doc_bloc.dart |  16 +-
 .../plugins/doc/application/doc_service.dart  |  20 ++-
 .../flowy-net/src/http_server/document.rs     |  10 +-
 .../flowy-net/src/local_server/server.rs      |  10 +-
 .../flowy-sdk/src/deps_resolve/folder_deps.rs |  18 +--
 .../src/deps_resolve/text_block_deps.rs       |  12 +-
 frontend/rust-lib/flowy-sdk/src/lib.rs        |   4 +-
 frontend/rust-lib/flowy-sdk/src/module.rs     |   6 +-
 .../rust-lib/flowy-text-block/src/editor.rs   |   7 +-
 .../rust-lib/flowy-text-block/src/entities.rs |  46 ++++++
 .../flowy-text-block/src/event_handler.rs     |  37 ++---
 .../flowy-text-block/src/event_map.rs         |  16 +-
 frontend/rust-lib/flowy-text-block/src/lib.rs |   8 +-
 .../rust-lib/flowy-text-block/src/manager.rs  | 119 +++++++-------
 .../rust-lib/flowy-text-block/src/queue.rs    |   6 +-
 .../flowy-text-block/tests/document/script.rs |   2 +-
 .../flowy-sync/src/entities/text_block.rs     |   2 +-
 shared-lib/lib-ot/src/core/document/node.rs   |   2 +-
 .../lib-ot/src/core/document/node_tree.rs     |   4 +-
 .../lib-ot/src/core/document/operation.rs     | 146 +++++++++++++-----
 .../lib-ot/src/core/document/transaction.rs   |  23 ++-
 shared-lib/lib-ot/src/text_delta/delta.rs     |   1 -
 shared-lib/lib-ot/tests/node/editor_test.rs   |   7 +-
 .../lib-ot/tests/node/operation_test.rs       |  63 +++++++-
 shared-lib/lib-ot/tests/node/script.rs        |  99 ++++++++++--
 shared-lib/lib-ot/tests/node/tree_test.rs     |  58 ++++---
 26 files changed, 515 insertions(+), 227 deletions(-)

diff --git a/frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart b/frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart
index ed12227af0..89c87db455 100644
--- a/frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart
+++ b/frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart
@@ -90,7 +90,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
     final result = await service.openDocument(docId: view.id);
     result.fold(
       (block) {
-        document = _decodeJsonToDocument(block.deltaStr);
+        document = _decodeJsonToDocument(block.snapshot);
         _subscription = document.changes.listen((event) {
           final delta = event.item2;
           final documentDelta = document.toDelta();
@@ -115,16 +115,12 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
   void _composeDelta(Delta composedDelta, Delta documentDelta) async {
     final json = jsonEncode(composedDelta.toJson());
     Log.debug("doc_id: $view.id - Send json: $json");
-    final result = await service.composeDelta(docId: view.id, data: json);
+    final result = await service.applyEdit(docId: view.id, data: json);
 
-    result.fold((rustDoc) {
-      // final json = utf8.decode(doc.data);
-      final rustDelta = Delta.fromJson(jsonDecode(rustDoc.deltaStr));
-      if (documentDelta != rustDelta) {
-        Log.error("Receive : $rustDelta");
-        Log.error("Expected : $documentDelta");
-      }
-    }, (r) => null);
+    result.fold(
+      (_) {},
+      (r) => Log.error(r),
+    );
   }
 
   Document _decodeJsonToDocument(String data) {
diff --git a/frontend/app_flowy/lib/plugins/doc/application/doc_service.dart b/frontend/app_flowy/lib/plugins/doc/application/doc_service.dart
index 659a99e371..067d51a510 100644
--- a/frontend/app_flowy/lib/plugins/doc/application/doc_service.dart
+++ b/frontend/app_flowy/lib/plugins/doc/application/doc_service.dart
@@ -4,22 +4,28 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-sync/text_block.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart';
 
 class DocumentService {
-  Future<Either<TextBlockDeltaPB, FlowyError>> openDocument({
+  Future<Either<TextBlockPB, FlowyError>> openDocument({
     required String docId,
   }) async {
     await FolderEventSetLatestView(ViewIdPB(value: docId)).send();
 
     final payload = TextBlockIdPB(value: docId);
-    return TextBlockEventGetBlockData(payload).send();
+    return TextBlockEventGetTextBlock(payload).send();
   }
 
-  Future<Either<TextBlockDeltaPB, FlowyError>> composeDelta({required String docId, required String data}) {
-    final payload = TextBlockDeltaPB.create()
-      ..blockId = docId
-      ..deltaStr = data;
-    return TextBlockEventApplyDelta(payload).send();
+  Future<Either<Unit, FlowyError>> applyEdit({
+    required String docId,
+    required String data,
+    String operations = "",
+  }) {
+    final payload = EditPayloadPB.create()
+      ..textBlockId = docId
+      ..operations = operations
+      ..delta = data;
+    return TextBlockEventApplyEdit(payload).send();
   }
 
   Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
diff --git a/frontend/rust-lib/flowy-net/src/http_server/document.rs b/frontend/rust-lib/flowy-net/src/http_server/document.rs
index ca9cb47955..b286e5c102 100644
--- a/frontend/rust-lib/flowy-net/src/http_server/document.rs
+++ b/frontend/rust-lib/flowy-net/src/http_server/document.rs
@@ -4,7 +4,7 @@ use crate::{
 };
 use flowy_error::FlowyError;
 use flowy_sync::entities::text_block::{CreateTextBlockParams, DocumentPB, ResetTextBlockParams, TextBlockIdPB};
-use flowy_text_block::BlockCloudService;
+use flowy_text_block::TextEditorCloudService;
 use http_flowy::response::FlowyResponse;
 use lazy_static::lazy_static;
 use lib_infra::future::FutureResult;
@@ -20,20 +20,20 @@ impl BlockHttpCloudService {
     }
 }
 
-impl BlockCloudService for BlockHttpCloudService {
-    fn create_block(&self, token: &str, params: CreateTextBlockParams) -> FutureResult<(), FlowyError> {
+impl TextEditorCloudService for BlockHttpCloudService {
+    fn create_text_block(&self, token: &str, params: CreateTextBlockParams) -> FutureResult<(), FlowyError> {
         let token = token.to_owned();
         let url = self.config.doc_url();
         FutureResult::new(async move { create_document_request(&token, params, &url).await })
     }
 
-    fn read_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError> {
+    fn read_text_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError> {
         let token = token.to_owned();
         let url = self.config.doc_url();
         FutureResult::new(async move { read_document_request(&token, params, &url).await })
     }
 
-    fn update_block(&self, token: &str, params: ResetTextBlockParams) -> FutureResult<(), FlowyError> {
+    fn update_text_block(&self, token: &str, params: ResetTextBlockParams) -> FutureResult<(), FlowyError> {
         let token = token.to_owned();
         let url = self.config.doc_url();
         FutureResult::new(async move { reset_doc_request(&token, params, &url).await })
diff --git a/frontend/rust-lib/flowy-net/src/local_server/server.rs b/frontend/rust-lib/flowy-net/src/local_server/server.rs
index 387ff4e885..060abf08bf 100644
--- a/frontend/rust-lib/flowy-net/src/local_server/server.rs
+++ b/frontend/rust-lib/flowy-net/src/local_server/server.rs
@@ -261,7 +261,7 @@ use flowy_folder::entities::{
 use flowy_folder_data_model::revision::{
     gen_app_id, gen_workspace_id, AppRevision, TrashRevision, ViewRevision, WorkspaceRevision,
 };
-use flowy_text_block::BlockCloudService;
+use flowy_text_block::TextEditorCloudService;
 use flowy_user::entities::{
     SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB,
 };
@@ -414,12 +414,12 @@ impl UserCloudService for LocalServer {
     }
 }
 
-impl BlockCloudService for LocalServer {
-    fn create_block(&self, _token: &str, _params: CreateTextBlockParams) -> FutureResult<(), FlowyError> {
+impl TextEditorCloudService for LocalServer {
+    fn create_text_block(&self, _token: &str, _params: CreateTextBlockParams) -> FutureResult<(), FlowyError> {
         FutureResult::new(async { Ok(()) })
     }
 
-    fn read_block(&self, _token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError> {
+    fn read_text_block(&self, _token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError> {
         let doc = DocumentPB {
             block_id: params.value,
             text: initial_quill_delta_string(),
@@ -429,7 +429,7 @@ impl BlockCloudService for LocalServer {
         FutureResult::new(async { Ok(Some(doc)) })
     }
 
-    fn update_block(&self, _token: &str, _params: ResetTextBlockParams) -> FutureResult<(), FlowyError> {
+    fn update_text_block(&self, _token: &str, _params: ResetTextBlockParams) -> FutureResult<(), FlowyError> {
         FutureResult::new(async { Ok(()) })
     }
 }
diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs
index 0dc51c1a09..79b5468a56 100644
--- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs
+++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs
@@ -18,7 +18,7 @@ use flowy_revision::{RevisionWebSocket, WSStateReceiver};
 use flowy_sync::client_document::default::initial_quill_delta_string;
 use flowy_sync::entities::revision::{RepeatedRevision, Revision};
 use flowy_sync::entities::ws_data::ClientRevisionWSData;
-use flowy_text_block::TextBlockManager;
+use flowy_text_block::TextEditorManager;
 use flowy_user::services::UserSession;
 use futures_core::future::BoxFuture;
 use lib_infra::future::{BoxResultFuture, FutureResult};
@@ -34,7 +34,7 @@ impl FolderDepsResolver {
         user_session: Arc<UserSession>,
         server_config: &ClientServerConfiguration,
         ws_conn: &Arc<FlowyWebSocketConnect>,
-        text_block_manager: &Arc<TextBlockManager>,
+        text_block_manager: &Arc<TextEditorManager>,
         grid_manager: &Arc<GridManager>,
     ) -> Arc<FolderManager> {
         let user: Arc<dyn WorkspaceUser> = Arc::new(WorkspaceUserImpl(user_session.clone()));
@@ -63,7 +63,7 @@ impl FolderDepsResolver {
 }
 
 fn make_view_data_processor(
-    text_block_manager: Arc<TextBlockManager>,
+    text_block_manager: Arc<TextEditorManager>,
     grid_manager: Arc<GridManager>,
 ) -> ViewDataProcessorMap {
     let mut map: HashMap<ViewDataTypePB, Arc<dyn ViewDataProcessor + Send + Sync>> = HashMap::new();
@@ -135,7 +135,7 @@ impl WSMessageReceiver for FolderWSMessageReceiverImpl {
     }
 }
 
-struct TextBlockViewDataProcessor(Arc<TextBlockManager>);
+struct TextBlockViewDataProcessor(Arc<TextEditorManager>);
 impl ViewDataProcessor for TextBlockViewDataProcessor {
     fn initialize(&self) -> FutureResult<(), FlowyError> {
         let manager = self.0.clone();
@@ -147,7 +147,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
         let view_id = view_id.to_string();
         let manager = self.0.clone();
         FutureResult::new(async move {
-            let _ = manager.create_block(view_id, repeated_revision).await?;
+            let _ = manager.create_text_block(view_id, repeated_revision).await?;
             Ok(())
         })
     }
@@ -156,7 +156,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
         let manager = self.0.clone();
         let view_id = view_id.to_string();
         FutureResult::new(async move {
-            let _ = manager.delete_block(view_id)?;
+            let _ = manager.close_text_editor(view_id)?;
             Ok(())
         })
     }
@@ -165,7 +165,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
         let manager = self.0.clone();
         let view_id = view_id.to_string();
         FutureResult::new(async move {
-            let _ = manager.close_block(view_id)?;
+            let _ = manager.close_text_editor(view_id)?;
             Ok(())
         })
     }
@@ -174,7 +174,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
         let view_id = view_id.to_string();
         let manager = self.0.clone();
         FutureResult::new(async move {
-            let editor = manager.open_block(view_id).await?;
+            let editor = manager.open_text_editor(view_id).await?;
             let delta_bytes = Bytes::from(editor.delta_str().await?);
             Ok(delta_bytes)
         })
@@ -195,7 +195,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
             let delta_data = Bytes::from(view_data);
             let repeated_revision: RepeatedRevision =
                 Revision::initial_revision(&user_id, &view_id, delta_data.clone()).into();
-            let _ = manager.create_block(view_id, repeated_revision).await?;
+            let _ = manager.create_text_block(view_id, repeated_revision).await?;
             Ok(delta_data)
         })
     }
diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/text_block_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/text_block_deps.rs
index 7b80e637ab..9fa5667bb1 100644
--- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/text_block_deps.rs
+++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/text_block_deps.rs
@@ -8,7 +8,7 @@ use flowy_revision::{RevisionWebSocket, WSStateReceiver};
 use flowy_sync::entities::ws_data::ClientRevisionWSData;
 use flowy_text_block::{
     errors::{internal_error, FlowyError},
-    BlockCloudService, TextBlockManager, TextBlockUser,
+    TextEditorCloudService, TextEditorManager, TextEditorUser,
 };
 use flowy_user::services::UserSession;
 use futures_core::future::BoxFuture;
@@ -23,15 +23,15 @@ impl TextBlockDepsResolver {
         ws_conn: Arc<FlowyWebSocketConnect>,
         user_session: Arc<UserSession>,
         server_config: &ClientServerConfiguration,
-    ) -> Arc<TextBlockManager> {
+    ) -> Arc<TextEditorManager> {
         let user = Arc::new(BlockUserImpl(user_session));
         let rev_web_socket = Arc::new(TextBlockWebSocket(ws_conn.clone()));
-        let cloud_service: Arc<dyn BlockCloudService> = match local_server {
+        let cloud_service: Arc<dyn TextEditorCloudService> = match local_server {
             None => Arc::new(BlockHttpCloudService::new(server_config.clone())),
             Some(local_server) => local_server,
         };
 
-        let manager = Arc::new(TextBlockManager::new(cloud_service, user, rev_web_socket));
+        let manager = Arc::new(TextEditorManager::new(cloud_service, user, rev_web_socket));
         let receiver = Arc::new(DocumentWSMessageReceiverImpl(manager.clone()));
         ws_conn.add_ws_message_receiver(receiver).unwrap();
 
@@ -40,7 +40,7 @@ impl TextBlockDepsResolver {
 }
 
 struct BlockUserImpl(Arc<UserSession>);
-impl TextBlockUser for BlockUserImpl {
+impl TextEditorUser for BlockUserImpl {
     fn user_dir(&self) -> Result<String, FlowyError> {
         let dir = self.0.user_dir().map_err(|e| FlowyError::unauthorized().context(e))?;
 
@@ -90,7 +90,7 @@ impl RevisionWebSocket for TextBlockWebSocket {
     }
 }
 
-struct DocumentWSMessageReceiverImpl(Arc<TextBlockManager>);
+struct DocumentWSMessageReceiverImpl(Arc<TextEditorManager>);
 impl WSMessageReceiver for DocumentWSMessageReceiverImpl {
     fn source(&self) -> WSChannel {
         WSChannel::Document
diff --git a/frontend/rust-lib/flowy-sdk/src/lib.rs b/frontend/rust-lib/flowy-sdk/src/lib.rs
index e0bd601987..e780f9f937 100644
--- a/frontend/rust-lib/flowy-sdk/src/lib.rs
+++ b/frontend/rust-lib/flowy-sdk/src/lib.rs
@@ -11,7 +11,7 @@ use flowy_net::{
     local_server::LocalServer,
     ws::connection::{listen_on_websocket, FlowyWebSocketConnect},
 };
-use flowy_text_block::TextBlockManager;
+use flowy_text_block::TextEditorManager;
 use flowy_user::services::{notifier::UserStatus, UserSession, UserSessionConfig};
 use lib_dispatch::prelude::*;
 use lib_dispatch::runtime::tokio_default_runtime;
@@ -89,7 +89,7 @@ pub struct FlowySDK {
     #[allow(dead_code)]
     config: FlowySDKConfig,
     pub user_session: Arc<UserSession>,
-    pub text_block_manager: Arc<TextBlockManager>,
+    pub text_block_manager: Arc<TextEditorManager>,
     pub folder_manager: Arc<FolderManager>,
     pub grid_manager: Arc<GridManager>,
     pub dispatcher: Arc<EventDispatcher>,
diff --git a/frontend/rust-lib/flowy-sdk/src/module.rs b/frontend/rust-lib/flowy-sdk/src/module.rs
index cbb1673a17..2f6a20d6c6 100644
--- a/frontend/rust-lib/flowy-sdk/src/module.rs
+++ b/frontend/rust-lib/flowy-sdk/src/module.rs
@@ -1,7 +1,7 @@
 use flowy_folder::manager::FolderManager;
 use flowy_grid::manager::GridManager;
 use flowy_net::ws::connection::FlowyWebSocketConnect;
-use flowy_text_block::TextBlockManager;
+use flowy_text_block::TextEditorManager;
 use flowy_user::services::UserSession;
 use lib_dispatch::prelude::Module;
 use std::sync::Arc;
@@ -11,7 +11,7 @@ pub fn mk_modules(
     folder_manager: &Arc<FolderManager>,
     grid_manager: &Arc<GridManager>,
     user_session: &Arc<UserSession>,
-    text_block_manager: &Arc<TextBlockManager>,
+    text_block_manager: &Arc<TextEditorManager>,
 ) -> Vec<Module> {
     let user_module = mk_user_module(user_session.clone());
     let folder_module = mk_folder_module(folder_manager.clone());
@@ -43,6 +43,6 @@ fn mk_grid_module(grid_manager: Arc<GridManager>) -> Module {
     flowy_grid::event_map::create(grid_manager)
 }
 
-fn mk_text_block_module(text_block_manager: Arc<TextBlockManager>) -> Module {
+fn mk_text_block_module(text_block_manager: Arc<TextEditorManager>) -> Module {
     flowy_text_block::event_map::create(text_block_manager)
 }
diff --git a/frontend/rust-lib/flowy-text-block/src/editor.rs b/frontend/rust-lib/flowy-text-block/src/editor.rs
index 8f75a67d88..8ca41f3f5a 100644
--- a/frontend/rust-lib/flowy-text-block/src/editor.rs
+++ b/frontend/rust-lib/flowy-text-block/src/editor.rs
@@ -2,7 +2,7 @@ use crate::web_socket::EditorCommandSender;
 use crate::{
     errors::FlowyError,
     queue::{EditBlockQueue, EditorCommand},
-    TextBlockUser,
+    TextEditorUser,
 };
 use bytes::Bytes;
 use flowy_error::{internal_error, FlowyResult};
@@ -24,7 +24,6 @@ use tokio::sync::{mpsc, oneshot};
 
 pub struct TextBlockEditor {
     pub doc_id: String,
-    #[allow(dead_code)]
     rev_manager: Arc<RevisionManager>,
     #[cfg(feature = "sync")]
     ws_manager: Arc<flowy_revision::RevisionWebSocketManager>,
@@ -35,7 +34,7 @@ impl TextBlockEditor {
     #[allow(unused_variables)]
     pub(crate) async fn new(
         doc_id: &str,
-        user: Arc<dyn TextBlockUser>,
+        user: Arc<dyn TextEditorUser>,
         mut rev_manager: RevisionManager,
         rev_web_socket: Arc<dyn RevisionWebSocket>,
         cloud_service: Arc<dyn RevisionCloudService>,
@@ -194,7 +193,7 @@ impl std::ops::Drop for TextBlockEditor {
 
 // The edit queue will exit after the EditorCommandSender was dropped.
 fn spawn_edit_queue(
-    user: Arc<dyn TextBlockUser>,
+    user: Arc<dyn TextEditorUser>,
     rev_manager: Arc<RevisionManager>,
     delta: TextDelta,
 ) -> EditorCommandSender {
diff --git a/frontend/rust-lib/flowy-text-block/src/entities.rs b/frontend/rust-lib/flowy-text-block/src/entities.rs
index ec8767285b..d7cc1d8665 100644
--- a/frontend/rust-lib/flowy-text-block/src/entities.rs
+++ b/frontend/rust-lib/flowy-text-block/src/entities.rs
@@ -29,6 +29,52 @@ impl std::convert::From<i32> for ExportType {
     }
 }
 
+#[derive(Default, ProtoBuf)]
+pub struct EditPayloadPB {
+    #[pb(index = 1)]
+    pub text_block_id: String,
+
+    // Encode in JSON format
+    #[pb(index = 2)]
+    pub operations: String,
+
+    // Encode in JSON format
+    #[pb(index = 3)]
+    pub delta: String,
+}
+
+#[derive(Default)]
+pub struct EditParams {
+    pub text_block_id: String,
+
+    // Encode in JSON format
+    pub operations: String,
+
+    // Encode in JSON format
+    pub delta: String,
+}
+
+impl TryInto<EditParams> for EditPayloadPB {
+    type Error = ErrorCode;
+    fn try_into(self) -> Result<EditParams, Self::Error> {
+        Ok(EditParams {
+            text_block_id: self.text_block_id,
+            operations: self.operations,
+            delta: self.delta,
+        })
+    }
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct TextBlockPB {
+    #[pb(index = 1)]
+    pub text_block_id: String,
+
+    /// Encode in JSON format
+    #[pb(index = 2)]
+    pub snapshot: String,
+}
+
 #[derive(Default, ProtoBuf)]
 pub struct ExportPayloadPB {
     #[pb(index = 1)]
diff --git a/frontend/rust-lib/flowy-text-block/src/event_handler.rs b/frontend/rust-lib/flowy-text-block/src/event_handler.rs
index dc9812d862..d7844ad12d 100644
--- a/frontend/rust-lib/flowy-text-block/src/event_handler.rs
+++ b/frontend/rust-lib/flowy-text-block/src/event_handler.rs
@@ -1,39 +1,40 @@
-use crate::entities::{ExportDataPB, ExportParams, ExportPayloadPB};
-use crate::TextBlockManager;
+use crate::entities::{EditParams, EditPayloadPB, ExportDataPB, ExportParams, ExportPayloadPB, TextBlockPB};
+use crate::TextEditorManager;
 use flowy_error::FlowyError;
 use flowy_sync::entities::text_block::{TextBlockDeltaPB, TextBlockIdPB};
 use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
 use std::convert::TryInto;
 use std::sync::Arc;
 
-pub(crate) async fn get_block_data_handler(
+pub(crate) async fn get_text_block_handler(
     data: Data<TextBlockIdPB>,
-    manager: AppData<Arc<TextBlockManager>>,
-) -> DataResult<TextBlockDeltaPB, FlowyError> {
-    let block_id: TextBlockIdPB = data.into_inner();
-    let editor = manager.open_block(&block_id).await?;
+    manager: AppData<Arc<TextEditorManager>>,
+) -> DataResult<TextBlockPB, FlowyError> {
+    let text_block_id: TextBlockIdPB = data.into_inner();
+    let editor = manager.open_text_editor(&text_block_id).await?;
     let delta_str = editor.delta_str().await?;
-    data_result(TextBlockDeltaPB {
-        block_id: block_id.into(),
-        delta_str,
+    data_result(TextBlockPB {
+        text_block_id: text_block_id.into(),
+        snapshot: delta_str,
     })
 }
 
-pub(crate) async fn apply_delta_handler(
-    data: Data<TextBlockDeltaPB>,
-    manager: AppData<Arc<TextBlockManager>>,
-) -> DataResult<TextBlockDeltaPB, FlowyError> {
-    let block_delta = manager.receive_local_delta(data.into_inner()).await?;
-    data_result(block_delta)
+pub(crate) async fn apply_edit_handler(
+    data: Data<EditPayloadPB>,
+    manager: AppData<Arc<TextEditorManager>>,
+) -> Result<(), FlowyError> {
+    let params: EditParams = data.into_inner().try_into()?;
+    let _ = manager.apply_edit(params).await?;
+    Ok(())
 }
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn export_handler(
     data: Data<ExportPayloadPB>,
-    manager: AppData<Arc<TextBlockManager>>,
+    manager: AppData<Arc<TextEditorManager>>,
 ) -> DataResult<ExportDataPB, FlowyError> {
     let params: ExportParams = data.into_inner().try_into()?;
-    let editor = manager.open_block(&params.view_id).await?;
+    let editor = manager.open_text_editor(&params.view_id).await?;
     let delta_json = editor.delta_str().await?;
     data_result(ExportDataPB {
         data: delta_json,
diff --git a/frontend/rust-lib/flowy-text-block/src/event_map.rs b/frontend/rust-lib/flowy-text-block/src/event_map.rs
index cfc06bf32c..d66ee490ee 100644
--- a/frontend/rust-lib/flowy-text-block/src/event_map.rs
+++ b/frontend/rust-lib/flowy-text-block/src/event_map.rs
@@ -1,16 +1,16 @@
 use crate::event_handler::*;
-use crate::TextBlockManager;
+use crate::TextEditorManager;
 use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
 use lib_dispatch::prelude::Module;
 use std::sync::Arc;
 use strum_macros::Display;
 
-pub fn create(block_manager: Arc<TextBlockManager>) -> Module {
+pub fn create(block_manager: Arc<TextEditorManager>) -> Module {
     let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(block_manager);
 
     module = module
-        .event(TextBlockEvent::GetBlockData, get_block_data_handler)
-        .event(TextBlockEvent::ApplyDelta, apply_delta_handler)
+        .event(TextBlockEvent::GetTextBlock, get_text_block_handler)
+        .event(TextBlockEvent::ApplyEdit, apply_edit_handler)
         .event(TextBlockEvent::ExportDocument, export_handler);
 
     module
@@ -19,11 +19,11 @@ pub fn create(block_manager: Arc<TextBlockManager>) -> Module {
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
 #[event_err = "FlowyError"]
 pub enum TextBlockEvent {
-    #[event(input = "TextBlockIdPB", output = "TextBlockDeltaPB")]
-    GetBlockData = 0,
+    #[event(input = "TextBlockIdPB", output = "TextBlockPB")]
+    GetTextBlock = 0,
 
-    #[event(input = "TextBlockDeltaPB", output = "TextBlockDeltaPB")]
-    ApplyDelta = 1,
+    #[event(input = "EditPayloadPB")]
+    ApplyEdit = 1,
 
     #[event(input = "ExportPayloadPB", output = "ExportDataPB")]
     ExportDocument = 2,
diff --git a/frontend/rust-lib/flowy-text-block/src/lib.rs b/frontend/rust-lib/flowy-text-block/src/lib.rs
index 37ddf6ea1e..b3f9839fde 100644
--- a/frontend/rust-lib/flowy-text-block/src/lib.rs
+++ b/frontend/rust-lib/flowy-text-block/src/lib.rs
@@ -18,10 +18,10 @@ use crate::errors::FlowyError;
 use flowy_sync::entities::text_block::{CreateTextBlockParams, DocumentPB, ResetTextBlockParams, TextBlockIdPB};
 use lib_infra::future::FutureResult;
 
-pub trait BlockCloudService: Send + Sync {
-    fn create_block(&self, token: &str, params: CreateTextBlockParams) -> FutureResult<(), FlowyError>;
+pub trait TextEditorCloudService: Send + Sync {
+    fn create_text_block(&self, token: &str, params: CreateTextBlockParams) -> FutureResult<(), FlowyError>;
 
-    fn read_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError>;
+    fn read_text_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError>;
 
-    fn update_block(&self, token: &str, params: ResetTextBlockParams) -> FutureResult<(), FlowyError>;
+    fn update_text_block(&self, token: &str, params: ResetTextBlockParams) -> FutureResult<(), FlowyError>;
 }
diff --git a/frontend/rust-lib/flowy-text-block/src/manager.rs b/frontend/rust-lib/flowy-text-block/src/manager.rs
index 57e875668e..424f7a8d50 100644
--- a/frontend/rust-lib/flowy-text-block/src/manager.rs
+++ b/frontend/rust-lib/flowy-text-block/src/manager.rs
@@ -1,5 +1,6 @@
+use crate::entities::{EditParams, EditPayloadPB};
 use crate::queue::TextBlockRevisionCompactor;
-use crate::{editor::TextBlockEditor, errors::FlowyError, BlockCloudService};
+use crate::{editor::TextBlockEditor, errors::FlowyError, TextEditorCloudService};
 use bytes::Bytes;
 use dashmap::DashMap;
 use flowy_database::ConnectionPool;
@@ -16,30 +17,30 @@ use flowy_sync::entities::{
 use lib_infra::future::FutureResult;
 use std::{convert::TryInto, sync::Arc};
 
-pub trait TextBlockUser: Send + Sync {
+pub trait TextEditorUser: Send + Sync {
     fn user_dir(&self) -> Result<String, FlowyError>;
     fn user_id(&self) -> Result<String, FlowyError>;
     fn token(&self) -> Result<String, FlowyError>;
     fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
 }
 
-pub struct TextBlockManager {
-    cloud_service: Arc<dyn BlockCloudService>,
+pub struct TextEditorManager {
+    cloud_service: Arc<dyn TextEditorCloudService>,
     rev_web_socket: Arc<dyn RevisionWebSocket>,
-    editor_map: Arc<TextBlockEditorMap>,
-    user: Arc<dyn TextBlockUser>,
+    editor_map: Arc<TextEditorMap>,
+    user: Arc<dyn TextEditorUser>,
 }
 
-impl TextBlockManager {
+impl TextEditorManager {
     pub fn new(
-        cloud_service: Arc<dyn BlockCloudService>,
-        text_block_user: Arc<dyn TextBlockUser>,
+        cloud_service: Arc<dyn TextEditorCloudService>,
+        text_block_user: Arc<dyn TextEditorUser>,
         rev_web_socket: Arc<dyn RevisionWebSocket>,
     ) -> Self {
         Self {
             cloud_service,
             rev_web_socket,
-            editor_map: Arc::new(TextBlockEditorMap::new()),
+            editor_map: Arc::new(TextEditorMap::new()),
             user: text_block_user,
         }
     }
@@ -50,45 +51,47 @@ impl TextBlockManager {
         Ok(())
     }
 
-    #[tracing::instrument(level = "trace", skip(self, block_id), fields(block_id), err)]
-    pub async fn open_block<T: AsRef<str>>(&self, block_id: T) -> Result<Arc<TextBlockEditor>, FlowyError> {
-        let block_id = block_id.as_ref();
-        tracing::Span::current().record("block_id", &block_id);
-        self.get_block_editor(block_id).await
+    #[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)]
+    pub async fn open_text_editor<T: AsRef<str>>(&self, editor_id: T) -> Result<Arc<TextBlockEditor>, FlowyError> {
+        let editor_id = editor_id.as_ref();
+        tracing::Span::current().record("editor_id", &editor_id);
+        self.get_text_editor(editor_id).await
     }
 
-    #[tracing::instrument(level = "trace", skip(self, block_id), fields(block_id), err)]
-    pub fn close_block<T: AsRef<str>>(&self, block_id: T) -> Result<(), FlowyError> {
-        let block_id = block_id.as_ref();
-        tracing::Span::current().record("block_id", &block_id);
-        self.editor_map.remove(block_id);
+    #[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)]
+    pub fn close_text_editor<T: AsRef<str>>(&self, editor_id: T) -> Result<(), FlowyError> {
+        let editor_id = editor_id.as_ref();
+        tracing::Span::current().record("editor_id", &editor_id);
+        self.editor_map.remove(editor_id);
         Ok(())
     }
 
-    #[tracing::instrument(level = "debug", skip(self, doc_id), fields(doc_id), err)]
-    pub fn delete_block<T: AsRef<str>>(&self, doc_id: T) -> Result<(), FlowyError> {
-        let doc_id = doc_id.as_ref();
-        tracing::Span::current().record("doc_id", &doc_id);
-        self.editor_map.remove(doc_id);
-        Ok(())
-    }
-
-    #[tracing::instrument(level = "debug", skip(self, delta), fields(doc_id = %delta.block_id), err)]
+    #[tracing::instrument(level = "debug", skip(self, delta), err)]
     pub async fn receive_local_delta(&self, delta: TextBlockDeltaPB) -> Result<TextBlockDeltaPB, FlowyError> {
-        let editor = self.get_block_editor(&delta.block_id).await?;
+        let editor = self.get_text_editor(&delta.text_block_id).await?;
         let _ = editor.compose_local_delta(Bytes::from(delta.delta_str)).await?;
-        let document_json = editor.delta_str().await?;
+        let delta_str = editor.delta_str().await?;
         Ok(TextBlockDeltaPB {
-            block_id: delta.block_id.clone(),
-            delta_str: document_json,
+            text_block_id: delta.text_block_id.clone(),
+            delta_str,
         })
     }
 
-    pub async fn create_block<T: AsRef<str>>(&self, doc_id: T, revisions: RepeatedRevision) -> FlowyResult<()> {
-        let doc_id = doc_id.as_ref().to_owned();
+    pub async fn apply_edit(&self, params: EditParams) -> FlowyResult<()> {
+        let editor = self.get_text_editor(&params.text_block_id).await?;
+        let _ = editor.compose_local_delta(Bytes::from(params.delta)).await?;
+        Ok(())
+    }
+
+    pub async fn create_text_block<T: AsRef<str>>(
+        &self,
+        text_block_id: T,
+        revisions: RepeatedRevision,
+    ) -> FlowyResult<()> {
+        let doc_id = text_block_id.as_ref().to_owned();
         let db_pool = self.user.db_pool()?;
         // Maybe we could save the block to disk without creating the RevisionManager
-        let rev_manager = self.make_rev_manager(&doc_id, db_pool)?;
+        let rev_manager = self.make_text_block_rev_manager(&doc_id, db_pool)?;
         let _ = rev_manager.reset_object(revisions).await?;
         Ok(())
     }
@@ -110,26 +113,26 @@ impl TextBlockManager {
     }
 }
 
-impl TextBlockManager {
-    async fn get_block_editor(&self, block_id: &str) -> FlowyResult<Arc<TextBlockEditor>> {
+impl TextEditorManager {
+    async fn get_text_editor(&self, block_id: &str) -> FlowyResult<Arc<TextBlockEditor>> {
         match self.editor_map.get(block_id) {
             None => {
                 let db_pool = self.user.db_pool()?;
-                self.make_text_block_editor(block_id, db_pool).await
+                self.make_text_editor(block_id, db_pool).await
             }
             Some(editor) => Ok(editor),
         }
     }
 
     #[tracing::instrument(level = "trace", skip(self, pool), err)]
-    async fn make_text_block_editor(
+    async fn make_text_editor(
         &self,
         block_id: &str,
         pool: Arc<ConnectionPool>,
     ) -> Result<Arc<TextBlockEditor>, FlowyError> {
         let user = self.user.clone();
         let token = self.user.token()?;
-        let rev_manager = self.make_rev_manager(block_id, pool.clone())?;
+        let rev_manager = self.make_text_block_rev_manager(block_id, pool.clone())?;
         let cloud_service = Arc::new(TextBlockRevisionCloudService {
             token,
             server: self.cloud_service.clone(),
@@ -140,7 +143,11 @@ impl TextBlockManager {
         Ok(doc_editor)
     }
 
-    fn make_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
+    fn make_text_block_rev_manager(
+        &self,
+        doc_id: &str,
+        pool: Arc<ConnectionPool>,
+    ) -> Result<RevisionManager, FlowyError> {
         let user_id = self.user.user_id()?;
         let disk_cache = SQLiteTextBlockRevisionPersistence::new(&user_id, pool.clone());
         let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache);
@@ -161,7 +168,7 @@ impl TextBlockManager {
 
 struct TextBlockRevisionCloudService {
     token: String,
-    server: Arc<dyn BlockCloudService>,
+    server: Arc<dyn TextEditorCloudService>,
 }
 
 impl RevisionCloudService for TextBlockRevisionCloudService {
@@ -173,7 +180,7 @@ impl RevisionCloudService for TextBlockRevisionCloudService {
         let user_id = user_id.to_string();
 
         FutureResult::new(async move {
-            match server.read_block(&token, params).await? {
+            match server.read_text_block(&token, params).await? {
                 None => Err(FlowyError::record_not_found().context("Remote doesn't have this document")),
                 Some(doc) => {
                     let delta_data = Bytes::from(doc.text.clone());
@@ -193,36 +200,36 @@ impl RevisionCloudService for TextBlockRevisionCloudService {
     }
 }
 
-pub struct TextBlockEditorMap {
+pub struct TextEditorMap {
     inner: DashMap<String, Arc<TextBlockEditor>>,
 }
 
-impl TextBlockEditorMap {
+impl TextEditorMap {
     fn new() -> Self {
         Self { inner: DashMap::new() }
     }
 
-    pub(crate) fn insert(&self, block_id: &str, doc: &Arc<TextBlockEditor>) {
-        if self.inner.contains_key(block_id) {
-            log::warn!("Doc:{} already exists in cache", block_id);
+    pub(crate) fn insert(&self, editor_id: &str, doc: &Arc<TextBlockEditor>) {
+        if self.inner.contains_key(editor_id) {
+            log::warn!("Doc:{} already exists in cache", editor_id);
         }
-        self.inner.insert(block_id.to_string(), doc.clone());
+        self.inner.insert(editor_id.to_string(), doc.clone());
     }
 
-    pub(crate) fn get(&self, block_id: &str) -> Option<Arc<TextBlockEditor>> {
-        Some(self.inner.get(block_id)?.clone())
+    pub(crate) fn get(&self, editor_id: &str) -> Option<Arc<TextBlockEditor>> {
+        Some(self.inner.get(editor_id)?.clone())
     }
 
-    pub(crate) fn remove(&self, block_id: &str) {
-        if let Some(editor) = self.get(block_id) {
+    pub(crate) fn remove(&self, editor_id: &str) {
+        if let Some(editor) = self.get(editor_id) {
             editor.stop()
         }
-        self.inner.remove(block_id);
+        self.inner.remove(editor_id);
     }
 }
 
 #[tracing::instrument(level = "trace", skip(web_socket, handlers))]
-fn listen_ws_state_changed(web_socket: Arc<dyn RevisionWebSocket>, handlers: Arc<TextBlockEditorMap>) {
+fn listen_ws_state_changed(web_socket: Arc<dyn RevisionWebSocket>, handlers: Arc<TextEditorMap>) {
     tokio::spawn(async move {
         let mut notify = web_socket.subscribe_state_changed().await;
         while let Ok(state) = notify.recv().await {
diff --git a/frontend/rust-lib/flowy-text-block/src/queue.rs b/frontend/rust-lib/flowy-text-block/src/queue.rs
index d81461d25e..510205c1ba 100644
--- a/frontend/rust-lib/flowy-text-block/src/queue.rs
+++ b/frontend/rust-lib/flowy-text-block/src/queue.rs
@@ -1,5 +1,5 @@
 use crate::web_socket::EditorCommandReceiver;
-use crate::TextBlockUser;
+use crate::TextEditorUser;
 use async_stream::stream;
 use bytes::Bytes;
 use flowy_error::{FlowyError, FlowyResult};
@@ -23,14 +23,14 @@ use tokio::sync::{oneshot, RwLock};
 // serial.
 pub(crate) struct EditBlockQueue {
     document: Arc<RwLock<ClientDocument>>,
-    user: Arc<dyn TextBlockUser>,
+    user: Arc<dyn TextEditorUser>,
     rev_manager: Arc<RevisionManager>,
     receiver: Option<EditorCommandReceiver>,
 }
 
 impl EditBlockQueue {
     pub(crate) fn new(
-        user: Arc<dyn TextBlockUser>,
+        user: Arc<dyn TextEditorUser>,
         rev_manager: Arc<RevisionManager>,
         delta: TextDelta,
         receiver: EditorCommandReceiver,
diff --git a/frontend/rust-lib/flowy-text-block/tests/document/script.rs b/frontend/rust-lib/flowy-text-block/tests/document/script.rs
index 254b94958e..ae3209020a 100644
--- a/frontend/rust-lib/flowy-text-block/tests/document/script.rs
+++ b/frontend/rust-lib/flowy-text-block/tests/document/script.rs
@@ -27,7 +27,7 @@ impl TextBlockEditorTest {
         let sdk = FlowySDKTest::default();
         let _ = sdk.init_user().await;
         let test = ViewTest::new_text_block_view(&sdk).await;
-        let editor = sdk.text_block_manager.open_block(&test.view.id).await.unwrap();
+        let editor = sdk.text_block_manager.open_text_editor(&test.view.id).await.unwrap();
         Self { sdk, editor }
     }
 
diff --git a/shared-lib/flowy-sync/src/entities/text_block.rs b/shared-lib/flowy-sync/src/entities/text_block.rs
index d65ec683ee..cca7222bd0 100644
--- a/shared-lib/flowy-sync/src/entities/text_block.rs
+++ b/shared-lib/flowy-sync/src/entities/text_block.rs
@@ -69,7 +69,7 @@ pub struct ResetTextBlockParams {
 #[derive(ProtoBuf, Default, Debug, Clone)]
 pub struct TextBlockDeltaPB {
     #[pb(index = 1)]
-    pub block_id: String,
+    pub text_block_id: String,
 
     #[pb(index = 2)]
     pub delta_str: String,
diff --git a/shared-lib/lib-ot/src/core/document/node.rs b/shared-lib/lib-ot/src/core/document/node.rs
index 18651a6062..908cfa3f18 100644
--- a/shared-lib/lib-ot/src/core/document/node.rs
+++ b/shared-lib/lib-ot/src/core/document/node.rs
@@ -6,7 +6,7 @@ use crate::errors::OTError;
 use crate::text_delta::TextDelta;
 use serde::{Deserialize, Serialize};
 
-#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq)]
+#[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
 pub struct NodeData {
     #[serde(rename = "type")]
     pub node_type: String,
diff --git a/shared-lib/lib-ot/src/core/document/node_tree.rs b/shared-lib/lib-ot/src/core/document/node_tree.rs
index 7f88224f24..87e8e427f3 100644
--- a/shared-lib/lib-ot/src/core/document/node_tree.rs
+++ b/shared-lib/lib-ot/src/core/document/node_tree.rs
@@ -216,7 +216,9 @@ impl NodeTree {
             return Ok(());
         }
 
-        if index == parent.children(&self.arena).count() {
+        /// Append the node to the end of the children list if index greater or equal to the
+        /// length of the children.
+        if index >= parent.children(&self.arena).count() {
             self.append_nodes(&parent, nodes);
             return Ok(());
         }
diff --git a/shared-lib/lib-ot/src/core/document/operation.rs b/shared-lib/lib-ot/src/core/document/operation.rs
index f9e772e5d0..9117a0aeb7 100644
--- a/shared-lib/lib-ot/src/core/document/operation.rs
+++ b/shared-lib/lib-ot/src/core/document/operation.rs
@@ -1,10 +1,10 @@
 use crate::core::attributes::Attributes;
 use crate::core::document::path::Path;
-use crate::core::{NodeBodyChangeset, NodeData};
+use crate::core::{NodeBodyChangeset, NodeData, OperationTransform};
 use crate::errors::OTError;
 use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 #[serde(tag = "op")]
 pub enum NodeOperation {
     #[serde(rename = "insert")]
@@ -27,8 +27,37 @@ pub enum NodeOperation {
     Delete { path: Path, nodes: Vec<NodeData> },
 }
 
+// impl OperationTransform for NodeOperation {
+//     fn compose(&self, other: &Self) -> Result<Self, OTError>
+//     where
+//         Self: Sized,
+//     {
+//         match self {
+//             NodeOperation::Insert { path, nodes } => {
+//                 let new_path = Path::transform(path, other.path(), nodes.len() as i64);
+//                 Ok((self.clone(), other.clone_with_new_path(new_path)))
+//             }
+//             NodeOperation::Delete { path, nodes } => {
+//                 let new_path = Path::transform(path, other.path(), nodes.len() as i64);
+//                 other.clone_with_new_path(new_path)
+//             }
+//             _ => other.clone(),
+//         }
+//     }
+//
+//     fn transform(&self, other: &Self) -> Result<(Self, Self), OTError>
+//     where
+//         Self: Sized,
+//     {
+//         todo!()
+//     }
+//
+//     fn invert(&self, other: &Self) -> Self {
+//         todo!()
+//     }
+// }
 impl NodeOperation {
-    pub fn path(&self) -> &Path {
+    pub fn get_path(&self) -> &Path {
         match self {
             NodeOperation::Insert { path, .. } => path,
             NodeOperation::UpdateAttributes { path, .. } => path,
@@ -36,6 +65,19 @@ impl NodeOperation {
             NodeOperation::UpdateBody { path, .. } => path,
         }
     }
+
+    pub fn mut_path<F>(&mut self, f: F)
+    where
+        F: FnOnce(&mut Path),
+    {
+        match self {
+            NodeOperation::Insert { path, .. } => f(path),
+            NodeOperation::UpdateAttributes { path, .. } => f(path),
+            NodeOperation::Delete { path, .. } => f(path),
+            NodeOperation::UpdateBody { path, .. } => f(path),
+        }
+    }
+
     pub fn invert(&self) -> NodeOperation {
         match self {
             NodeOperation::Insert { path, nodes } => NodeOperation::Delete {
@@ -61,47 +103,73 @@ impl NodeOperation {
             },
         }
     }
-    pub fn clone_with_new_path(&self, path: Path) -> NodeOperation {
+
+    /// Transform the `other` operation into a new operation that carries the changes made by
+    /// the current operation.
+    ///
+    /// # Arguments
+    ///
+    /// * `other`: The operation that is going to be transformed
+    ///
+    /// returns: NodeOperation
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use lib_ot::core::{NodeDataBuilder, NodeOperation, Path};
+    /// let node_1 = NodeDataBuilder::new("text_1").build();
+    /// let node_2 = NodeDataBuilder::new("text_2").build();
+    ///
+    /// let op_1 = NodeOperation::Insert {
+    ///     path: Path(vec![0, 1]),
+    ///     nodes: vec![node_1],
+    /// };
+    ///
+    /// let mut op_2 = NodeOperation::Insert {
+    ///     path: Path(vec![0, 1]),
+    ///     nodes: vec![node_2],
+    /// };
+    ///
+    /// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,1],
+    /// "nodes":[{"type":"text_2"}]}"#);
+    ///
+    /// let new_op = op_1.transform(&op_2);
+    /// assert_eq!(serde_json::to_string(&new_op).unwrap(), r#"{"op":"insert","path":[0,2],
+    /// "nodes":[{"type":"text_2"}]}"#);
+    ///
+    /// ```
+    pub fn transform(&self, other: &NodeOperation) -> NodeOperation {
+        let mut other = other.clone();
         match self {
-            NodeOperation::Insert { nodes, .. } => NodeOperation::Insert {
-                path,
-                nodes: nodes.clone(),
-            },
-            NodeOperation::UpdateAttributes {
-                attributes,
-                old_attributes,
-                ..
-            } => NodeOperation::UpdateAttributes {
-                path,
-                attributes: attributes.clone(),
-                old_attributes: old_attributes.clone(),
-            },
-            NodeOperation::Delete { nodes, .. } => NodeOperation::Delete {
-                path,
-                nodes: nodes.clone(),
-            },
-            NodeOperation::UpdateBody { path, changeset } => NodeOperation::UpdateBody {
-                path: path.clone(),
-                changeset: changeset.clone(),
-            },
-        }
-    }
-    pub fn transform(a: &NodeOperation, b: &NodeOperation) -> NodeOperation {
-        match a {
-            NodeOperation::Insert { path: a_path, nodes } => {
-                let new_path = Path::transform(a_path, b.path(), nodes.len() as i64);
-                b.clone_with_new_path(new_path)
+            NodeOperation::Insert { path, nodes } => {
+                let new_path = Path::transform(path, other.get_path(), nodes.len() as i64);
+                other.mut_path(|path| *path = new_path);
             }
             NodeOperation::Delete { path: a_path, nodes } => {
-                let new_path = Path::transform(a_path, b.path(), nodes.len() as i64);
-                b.clone_with_new_path(new_path)
+                let new_path = Path::transform(a_path, other.get_path(), nodes.len() as i64);
+                other.mut_path(|path| *path = new_path);
             }
-            _ => b.clone(),
+            _ => {}
+        }
+        other
+    }
+
+    pub fn mut_transform(&self, other: &mut NodeOperation) {
+        match self {
+            NodeOperation::Insert { path, nodes } => {
+                let new_path = Path::transform(path, other.get_path(), nodes.len() as i64);
+                other.mut_path(|path| *path = new_path);
+            }
+            NodeOperation::Delete { path: a_path, nodes } => {
+                let new_path = Path::transform(a_path, other.get_path(), nodes.len() as i64);
+                other.mut_path(|path| *path = new_path);
+            }
+            _ => {}
         }
     }
 }
 
-#[derive(Serialize, Deserialize, Default)]
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
 pub struct NodeOperationList {
     operations: Vec<NodeOperation>,
 }
@@ -126,6 +194,12 @@ impl std::ops::DerefMut for NodeOperationList {
     }
 }
 
+impl std::convert::From<Vec<NodeOperation>> for NodeOperationList {
+    fn from(operations: Vec<NodeOperation>) -> Self {
+        Self { operations }
+    }
+}
+
 impl NodeOperationList {
     pub fn new(operations: Vec<NodeOperation>) -> Self {
         Self { operations }
diff --git a/shared-lib/lib-ot/src/core/document/transaction.rs b/shared-lib/lib-ot/src/core/document/transaction.rs
index 9b48ddd92d..20e58fe1ec 100644
--- a/shared-lib/lib-ot/src/core/document/transaction.rs
+++ b/shared-lib/lib-ot/src/core/document/transaction.rs
@@ -5,18 +5,35 @@ use indextree::NodeId;
 
 use super::{NodeBodyChangeset, NodeOperationList};
 
+#[derive(Debug, Clone)]
 pub struct Transaction {
     operations: NodeOperationList,
 }
 
 impl Transaction {
-    pub fn new(operations: NodeOperationList) -> Transaction {
-        Transaction { operations }
+    pub fn new() -> Self {
+        Transaction {
+            operations: vec![].into(),
+        }
+    }
+
+    pub fn from_operations<T: Into<NodeOperationList>>(operations: T) -> Self {
+        Self {
+            operations: operations.into(),
+        }
     }
 
     pub fn into_operations(self) -> Vec<NodeOperation> {
         self.operations.into_inner()
     }
+
+    pub fn transform(&self, other: &mut Transaction) {
+        for other_operation in other.iter_mut() {
+            for operation in self.operations.iter() {
+                operation.mut_transform(other_operation);
+            }
+        }
+    }
 }
 
 impl std::ops::Deref for Transaction {
@@ -177,6 +194,6 @@ impl<'a> TransactionBuilder<'a> {
     }
 
     pub fn finalize(self) -> Transaction {
-        Transaction::new(self.operations)
+        Transaction::from_operations(self.operations)
     }
 }
diff --git a/shared-lib/lib-ot/src/text_delta/delta.rs b/shared-lib/lib-ot/src/text_delta/delta.rs
index c559f9cb0f..e8eb3c3fd8 100644
--- a/shared-lib/lib-ot/src/text_delta/delta.rs
+++ b/shared-lib/lib-ot/src/text_delta/delta.rs
@@ -2,5 +2,4 @@ use crate::core::{Attributes, Operation, OperationBuilder, Operations};
 
 pub type TextDelta = Operations<Attributes>;
 pub type TextDeltaBuilder = OperationBuilder<Attributes>;
-
 pub type TextOperation = Operation<Attributes>;
diff --git a/shared-lib/lib-ot/tests/node/editor_test.rs b/shared-lib/lib-ot/tests/node/editor_test.rs
index 6136fe5efd..abd294ecc0 100644
--- a/shared-lib/lib-ot/tests/node/editor_test.rs
+++ b/shared-lib/lib-ot/tests/node/editor_test.rs
@@ -26,7 +26,8 @@ fn editor_deserialize_node_test() {
     test.run_scripts(vec![
         InsertNode {
             path,
-            node: node.clone(),
+            node_data: node.clone(),
+            rev_id: 1,
         },
         AssertNumberOfNodesAtPath { path: None, len: 1 },
         AssertNumberOfNodesAtPath {
@@ -41,11 +42,11 @@ fn editor_deserialize_node_test() {
             path: vec![0, 1].into(),
             expected: expected_delta,
         },
-        AssertNode {
+        AssertNodeData {
             path: vec![0, 0].into(),
             expected: Some(node.children[0].clone()),
         },
-        AssertNode {
+        AssertNodeData {
             path: vec![0, 3].into(),
             expected: Some(node.children[3].clone()),
         },
diff --git a/shared-lib/lib-ot/tests/node/operation_test.rs b/shared-lib/lib-ot/tests/node/operation_test.rs
index 6a7f8bb25a..d60b035c1c 100644
--- a/shared-lib/lib-ot/tests/node/operation_test.rs
+++ b/shared-lib/lib-ot/tests/node/operation_test.rs
@@ -1,4 +1,6 @@
-use lib_ot::core::AttributeBuilder;
+use crate::node::script::NodeScript::*;
+use crate::node::script::NodeTest;
+use lib_ot::core::{AttributeBuilder, Node, NodeTree, Transaction, TransactionBuilder};
 use lib_ot::{
     core::{NodeBodyChangeset, NodeData, NodeDataBuilder, NodeOperation, Path},
     text_delta::TextDeltaBuilder,
@@ -69,3 +71,62 @@ fn operation_update_node_body_deserialize_test() {
     let json_2 = serde_json::to_string(&operation).unwrap();
     assert_eq!(json_1, json_2);
 }
+
+#[test]
+fn operation_insert_transform_test() {
+    let node_1 = NodeDataBuilder::new("text_1").build();
+    let node_2 = NodeDataBuilder::new("text_2").build();
+    let op_1 = NodeOperation::Insert {
+        path: Path(vec![0, 1]),
+        nodes: vec![node_1],
+    };
+
+    let mut insert_2 = NodeOperation::Insert {
+        path: Path(vec![0, 1]),
+        nodes: vec![node_2],
+    };
+
+    // let mut node_tree = NodeTree::new("root");
+    // node_tree.apply_op(insert_1.clone()).unwrap();
+
+    let new_op = op_1.transform(&insert_2);
+    let json = serde_json::to_string(&new_op).unwrap();
+    assert_eq!(json, r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#);
+}
+
+#[test]
+fn operation_insert_transform_test2() {
+    let mut test = NodeTest::new();
+    let node_data_1 = NodeDataBuilder::new("text_1").build();
+    let node_data_2 = NodeDataBuilder::new("text_2").build();
+    let node_2: Node = node_data_2.clone().into();
+    let node_data_3 = NodeDataBuilder::new("text_3").build();
+    let node_3: Node = node_data_3.clone().into();
+
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: node_data_1.clone(),
+            rev_id: 1,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: node_data_2.clone(),
+            rev_id: 2,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: node_data_3.clone(),
+            rev_id: 1,
+        },
+        // AssertNode {
+        //     path: 2.into(),
+        //     expected: node_2,
+        // },
+        AssertNode {
+            path: 1.into(),
+            expected: node_3,
+        },
+    ];
+    test.run_scripts(scripts);
+}
diff --git a/shared-lib/lib-ot/tests/node/script.rs b/shared-lib/lib-ot/tests/node/script.rs
index 2aac683de8..01d1ad16db 100644
--- a/shared-lib/lib-ot/tests/node/script.rs
+++ b/shared-lib/lib-ot/tests/node/script.rs
@@ -1,26 +1,61 @@
+use lib_ot::core::{Node, NodeOperation, Transaction};
 use lib_ot::{
     core::attributes::Attributes,
     core::{NodeBody, NodeBodyChangeset, NodeData, NodeTree, Path, TransactionBuilder},
     text_delta::TextDelta,
 };
+use std::collections::HashMap;
 
 pub enum NodeScript {
-    InsertNode { path: Path, node: NodeData },
-    UpdateAttributes { path: Path, attributes: Attributes },
-    UpdateBody { path: Path, changeset: NodeBodyChangeset },
-    DeleteNode { path: Path },
-    AssertNumberOfNodesAtPath { path: Option<Path>, len: usize },
-    AssertNode { path: Path, expected: Option<NodeData> },
-    AssertNodeDelta { path: Path, expected: TextDelta },
+    InsertNode {
+        path: Path,
+        node_data: NodeData,
+        rev_id: usize,
+    },
+    UpdateAttributes {
+        path: Path,
+        attributes: Attributes,
+    },
+    UpdateBody {
+        path: Path,
+        changeset: NodeBodyChangeset,
+    },
+    DeleteNode {
+        path: Path,
+    },
+    AssertNumberOfNodesAtPath {
+        path: Option<Path>,
+        len: usize,
+    },
+    AssertNodeData {
+        path: Path,
+        expected: Option<NodeData>,
+    },
+    AssertNode {
+        path: Path,
+        expected: Node,
+    },
+    AssertNodeDelta {
+        path: Path,
+        expected: TextDelta,
+    },
+    ApplyTransaction {
+        transaction: Transaction,
+        rev_id: usize,
+    },
 }
 
 pub struct NodeTest {
+    rev_id: usize,
+    rev_operations: HashMap<usize, Transaction>,
     node_tree: NodeTree,
 }
 
 impl NodeTest {
     pub fn new() -> Self {
         Self {
+            rev_id: 0,
+            rev_operations: HashMap::new(),
             node_tree: NodeTree::new("root"),
         }
     }
@@ -33,40 +68,49 @@ impl NodeTest {
 
     pub fn run_script(&mut self, script: NodeScript) {
         match script {
-            NodeScript::InsertNode { path, node } => {
-                let transaction = TransactionBuilder::new(&self.node_tree)
+            NodeScript::InsertNode {
+                path,
+                node_data: node,
+                rev_id,
+            } => {
+                let mut transaction = TransactionBuilder::new(&self.node_tree)
                     .insert_node_at_path(path, node)
                     .finalize();
-
-                self.node_tree.apply(transaction).unwrap();
+                self.transform_transaction_if_need(&mut transaction, rev_id);
+                self.apply_transaction(transaction);
             }
             NodeScript::UpdateAttributes { path, attributes } => {
                 let transaction = TransactionBuilder::new(&self.node_tree)
                     .update_attributes_at_path(&path, attributes)
                     .finalize();
-                self.node_tree.apply(transaction).unwrap();
+                self.apply_transaction(transaction);
             }
             NodeScript::UpdateBody { path, changeset } => {
                 //
                 let transaction = TransactionBuilder::new(&self.node_tree)
                     .update_body_at_path(&path, changeset)
                     .finalize();
-                self.node_tree.apply(transaction).unwrap();
+                self.apply_transaction(transaction);
             }
             NodeScript::DeleteNode { path } => {
                 let transaction = TransactionBuilder::new(&self.node_tree)
                     .delete_node_at_path(&path)
                     .finalize();
-                self.node_tree.apply(transaction).unwrap();
+                self.apply_transaction(transaction);
             }
             NodeScript::AssertNode { path, expected } => {
+                let node_id = self.node_tree.node_id_at_path(path).unwrap();
+                let node = self.node_tree.get_node(node_id).cloned().unwrap();
+                assert_eq!(node, expected);
+            }
+            NodeScript::AssertNodeData { path, expected } => {
                 let node_id = self.node_tree.node_id_at_path(path);
 
                 match node_id {
                     None => assert!(node_id.is_none()),
                     Some(node_id) => {
-                        let node_data = self.node_tree.get_node(node_id).cloned();
-                        assert_eq!(node_data, expected.map(|e| e.into()));
+                        let node = self.node_tree.get_node(node_id).cloned();
+                        assert_eq!(node, expected.map(|e| e.into()));
                     }
                 }
             }
@@ -92,6 +136,29 @@ impl NodeTest {
                     panic!("Node body type not match, expect Delta");
                 }
             }
+
+            NodeScript::ApplyTransaction {
+                mut transaction,
+                rev_id,
+            } => {
+                self.transform_transaction_if_need(&mut transaction, rev_id);
+                self.apply_transaction(transaction);
+            }
+        }
+    }
+
+    fn apply_transaction(&mut self, transaction: Transaction) {
+        self.rev_id += 1;
+        self.rev_operations.insert(self.rev_id, transaction.clone());
+        self.node_tree.apply(transaction).unwrap();
+    }
+
+    fn transform_transaction_if_need(&mut self, transaction: &mut Transaction, rev_id: usize) {
+        if self.rev_id >= rev_id {
+            for rev_id in rev_id..self.rev_id {
+                let old_transaction = self.rev_operations.get(&rev_id).unwrap();
+                old_transaction.transform(transaction);
+            }
         }
     }
 }
diff --git a/shared-lib/lib-ot/tests/node/tree_test.rs b/shared-lib/lib-ot/tests/node/tree_test.rs
index 95c05ea25a..39357b158c 100644
--- a/shared-lib/lib-ot/tests/node/tree_test.rs
+++ b/shared-lib/lib-ot/tests/node/tree_test.rs
@@ -14,9 +14,10 @@ fn node_insert_test() {
     let scripts = vec![
         InsertNode {
             path: path.clone(),
-            node: inserted_node.clone(),
+            node_data: inserted_node.clone(),
+            rev_id: 1,
         },
-        AssertNode {
+        AssertNodeData {
             path,
             expected: Some(inserted_node),
         },
@@ -32,9 +33,10 @@ fn node_insert_node_with_children_test() {
     let scripts = vec![
         InsertNode {
             path: path.clone(),
-            node: inserted_node.clone(),
+            node_data: inserted_node.clone(),
+            rev_id: 1,
         },
-        AssertNode {
+        AssertNodeData {
             path,
             expected: Some(inserted_node),
         },
@@ -57,25 +59,28 @@ fn node_insert_multi_nodes_test() {
     let scripts = vec![
         InsertNode {
             path: path_1.clone(),
-            node: node_1.clone(),
+            node_data: node_1.clone(),
+            rev_id: 1,
         },
         InsertNode {
             path: path_2.clone(),
-            node: node_2.clone(),
+            node_data: node_2.clone(),
+            rev_id: 2,
         },
         InsertNode {
             path: path_3.clone(),
-            node: node_3.clone(),
+            node_data: node_3.clone(),
+            rev_id: 3,
         },
-        AssertNode {
+        AssertNodeData {
             path: path_1,
             expected: Some(node_1),
         },
-        AssertNode {
+        AssertNodeData {
             path: path_2,
             expected: Some(node_2),
         },
-        AssertNode {
+        AssertNodeData {
             path: path_3,
             expected: Some(node_3),
         },
@@ -101,35 +106,39 @@ fn node_insert_node_in_ordered_nodes_test() {
     let scripts = vec![
         InsertNode {
             path: path_1.clone(),
-            node: node_1.clone(),
+            node_data: node_1.clone(),
+            rev_id: 1,
         },
         InsertNode {
             path: path_2.clone(),
-            node: node_2_1.clone(),
+            node_data: node_2_1.clone(),
+            rev_id: 2,
         },
         InsertNode {
             path: path_3.clone(),
-            node: node_3.clone(),
+            node_data: node_3.clone(),
+            rev_id: 3,
         },
         // 0:note_1 , 1: note_2_1, 2: note_3
         InsertNode {
             path: path_2.clone(),
-            node: node_2_2.clone(),
+            node_data: node_2_2.clone(),
+            rev_id: 4,
         },
         // 0:note_1 , 1:note_2_2,  2: note_2_1, 3: note_3
-        AssertNode {
+        AssertNodeData {
             path: path_1,
             expected: Some(node_1),
         },
-        AssertNode {
+        AssertNodeData {
             path: path_2,
             expected: Some(node_2_2),
         },
-        AssertNode {
+        AssertNodeData {
             path: path_3,
             expected: Some(node_2_1),
         },
-        AssertNode {
+        AssertNodeData {
             path: path_4,
             expected: Some(node_3),
         },
@@ -149,13 +158,14 @@ fn node_insert_with_attributes_test() {
     let scripts = vec![
         InsertNode {
             path: path.clone(),
-            node: inserted_node.clone(),
+            node_data: inserted_node.clone(),
+            rev_id: 1,
         },
         UpdateAttributes {
             path: path.clone(),
             attributes: inserted_node.attributes.clone(),
         },
-        AssertNode {
+        AssertNodeData {
             path,
             expected: Some(inserted_node),
         },
@@ -172,10 +182,11 @@ fn node_delete_test() {
     let scripts = vec![
         InsertNode {
             path: path.clone(),
-            node: inserted_node,
+            node_data: inserted_node,
+            rev_id: 1,
         },
         DeleteNode { path: path.clone() },
-        AssertNode { path, expected: None },
+        AssertNodeData { path, expected: None },
     ];
     test.run_scripts(scripts);
 }
@@ -198,7 +209,8 @@ fn node_update_body_test() {
     let scripts = vec![
         InsertNode {
             path: path.clone(),
-            node,
+            node_data: node,
+            rev_id: 1,
         },
         UpdateBody {
             path: path.clone(),