Nathan.fooo преди 2 години
родител
ревизия
0f3c6d6a04
променени са 100 файла, в които са добавени 2571 реда и са изтрити 1127 реда
  1. 21 21
      frontend/appflowy_flutter/test/bloc_test/board_test/group_by_multi_select_field_test.dart
  2. 2 0
      frontend/appflowy_tauri/src-tauri/.cargo/config.toml
  3. 96 74
      frontend/appflowy_tauri/src-tauri/Cargo.lock
  4. 1 1
      frontend/appflowy_tauri/src-tauri/Cargo.toml
  5. 1 1
      frontend/appflowy_tauri/src-tauri/build.rs
  6. 16 15
      frontend/appflowy_tauri/src-tauri/src/init.rs
  7. 0 1
      frontend/appflowy_tauri/src-tauri/src/request.rs
  8. 2 3
      frontend/appflowy_tauri/src-tauri/tauri.conf.json
  9. 2 0
      frontend/appflowy_tauri/src/appflowy_app/App.tsx
  10. 81 17
      frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/DatabaseTestHelper.ts
  11. 22 2
      frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/TestAPI.tsx
  12. 277 38
      frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/TestGrid.tsx
  13. 31 0
      frontend/appflowy_tauri/src/appflowy_app/components/auth/GetStarted/GetStarted.tsx
  14. 26 3
      frontend/appflowy_tauri/src/appflowy_app/components/auth/Login/Login.hooks.ts
  15. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx
  16. 23 17
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_cache.ts
  17. 54 54
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_controller.ts
  18. 15 12
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_observer.ts
  19. 9 54
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/controller_builder.ts
  20. 18 17
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_parser.ts
  21. 0 2
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_persistence.ts
  22. 26 11
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/select_option_bd_svc.ts
  23. 1 2
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_bd_svc.ts
  24. 12 10
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_controller.ts
  25. 1 2
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_bd_svc.ts
  26. 29 32
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_controller.ts
  27. 49 13
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_observer.ts
  28. 38 0
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_bd_svc.ts
  29. 205 0
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_context.ts
  30. 127 0
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_controller.ts
  31. 3 4
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/notifications/observer.ts
  32. 32 0
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/row/row_bd_svc.ts
  33. 47 39
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/row/row_cache.ts
  34. 22 22
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/view/database_view_cache.ts
  35. 23 22
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/view/view_row_observer.ts
  36. 3 4
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/app/app_observer.ts
  37. 2 2
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/view/view_observer.ts
  38. 18 15
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/workspace/workspace_observer.ts
  39. 2 2
      frontend/appflowy_tauri/src/appflowy_app/stores/reducers/current-user/slice.ts
  40. 12 6
      frontend/appflowy_tauri/src/services/backend/notifications/observer.ts
  41. 0 24
      frontend/rust-lib/.cargo/config.toml
  42. 444 85
      frontend/rust-lib/Cargo.lock
  43. 2 0
      frontend/rust-lib/dart-ffi/.cargo/config.toml
  44. 6 6
      frontend/rust-lib/dart-ffi/Cargo.toml
  45. 2 2
      frontend/rust-lib/flowy-ast/Cargo.toml
  46. 4 4
      frontend/rust-lib/flowy-client-sync/Cargo.toml
  47. 7 7
      frontend/rust-lib/flowy-codegen/Cargo.toml
  48. 3 2
      frontend/rust-lib/flowy-codegen/src/ts_event/event_template.tera
  49. 65 63
      frontend/rust-lib/flowy-core/Cargo.toml
  50. 10 0
      frontend/rust-lib/flowy-core/src/lib.rs
  51. 14 14
      frontend/rust-lib/flowy-database/Cargo.toml
  52. 1 1
      frontend/rust-lib/flowy-database/src/entities/row_entities.rs
  53. 2 2
      frontend/rust-lib/flowy-database/src/event_handler.rs
  54. 15 18
      frontend/rust-lib/flowy-database/src/manager.rs
  55. 2 2
      frontend/rust-lib/flowy-database/src/services/database/database_editor.rs
  56. 10 13
      frontend/rust-lib/flowy-database/src/services/database_view/editor.rs
  57. 27 17
      frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs
  58. 1 0
      frontend/rust-lib/flowy-database/src/services/database_view/notifier.rs
  59. 1 0
      frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs
  60. 11 6
      frontend/rust-lib/flowy-database/src/services/filter/controller.rs
  61. 20 13
      frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/block_impl.rs
  62. 11 4
      frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/database_impl.rs
  63. 20 13
      frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/view_impl.rs
  64. 11 6
      frontend/rust-lib/flowy-database/src/services/sort/controller.rs
  65. 5 5
      frontend/rust-lib/flowy-derive/Cargo.toml
  66. 10 10
      frontend/rust-lib/flowy-document/Cargo.toml
  67. 1 0
      frontend/rust-lib/flowy-document/src/editor/queue.rs
  68. 1 0
      frontend/rust-lib/flowy-document/src/old_editor/queue.rs
  69. 1 0
      frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_snapshot.rs
  70. 3 3
      frontend/rust-lib/flowy-error/Cargo.toml
  71. 6 6
      frontend/rust-lib/flowy-folder/Cargo.toml
  72. 11 4
      frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs
  73. 0 3
      frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs
  74. 7 7
      frontend/rust-lib/flowy-net/Cargo.toml
  75. 2 2
      frontend/rust-lib/flowy-notification/Cargo.toml
  76. 6 6
      frontend/rust-lib/flowy-revision/Cargo.toml
  77. 1 0
      frontend/rust-lib/flowy-revision/src/rev_queue.rs
  78. 23 23
      frontend/rust-lib/flowy-sqlite/Cargo.toml
  79. 3 3
      frontend/rust-lib/flowy-task/Cargo.toml
  80. 21 31
      frontend/rust-lib/flowy-task/src/scheduler.rs
  81. 8 8
      frontend/rust-lib/flowy-test/Cargo.toml
  82. 5 5
      frontend/rust-lib/flowy-user/Cargo.toml
  83. 9 9
      frontend/rust-lib/lib-dispatch/Cargo.toml
  84. 5 5
      frontend/rust-lib/lib-log/Cargo.toml
  85. 353 117
      shared-lib/Cargo.lock
  86. 2 2
      shared-lib/database-model/Cargo.toml
  87. 1 1
      shared-lib/flowy-client-network-config/Cargo.toml
  88. 2 2
      shared-lib/flowy-client-ws/Cargo.toml
  89. 5 5
      shared-lib/flowy-server-sync/Cargo.toml
  90. 3 6
      shared-lib/flowy-server-sync/src/server_document/document_manager.rs
  91. 3 6
      shared-lib/flowy-server-sync/src/server_folder/folder_manager.rs
  92. 1 1
      shared-lib/flowy-sync/Cargo.toml
  93. 4 4
      shared-lib/lib-infra/Cargo.toml
  94. 3 3
      shared-lib/lib-ot/Cargo.toml
  95. 9 9
      shared-lib/lib-ws/Cargo.toml
  96. 1 1
      shared-lib/revision-model/Cargo.toml
  97. 6 6
      shared-lib/user-model/Cargo.toml
  98. 3 4
      shared-lib/user-model/src/parser/user_email.rs
  99. 6 7
      shared-lib/user-model/src/parser/user_name.rs
  100. 1 1
      shared-lib/ws-model/Cargo.toml

+ 21 - 21
frontend/appflowy_flutter/test/bloc_test/board_test/group_by_multi_select_field_test.dart

@@ -1,6 +1,6 @@
 import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
 import 'package:appflowy/plugins/database_view/application/setting/group_bloc.dart';
-import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
+// import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
 import 'package:appflowy/plugins/database_view/grid/application/cell/select_option_editor_bloc.dart';
 import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -37,15 +37,15 @@ void main() {
     await boardResponseFuture();
 
     //assert only have the 'No status' group
-    final boardBloc = BoardBloc(view: context.gridView)
-      ..add(const BoardEvent.initial());
-    await boardResponseFuture();
-    assert(boardBloc.groupControllers.values.length == 1,
-        "Expected 1, but receive ${boardBloc.groupControllers.values.length}");
-    final expectedGroupName = "No ${multiSelectField.name}";
-    assert(
-        boardBloc.groupControllers.values.first.group.desc == expectedGroupName,
-        "Expected $expectedGroupName, but receive ${boardBloc.groupControllers.values.first.group.desc}");
+    // final boardBloc = BoardBloc(view: context.gridView)
+    //   ..add(const BoardEvent.initial());
+    // await boardResponseFuture();
+    // assert(boardBloc.groupControllers.values.length == 1,
+    //     "Expected 1, but receive ${boardBloc.groupControllers.values.length}");
+    // final expectedGroupName = "No ${multiSelectField.name}";
+    // assert(
+    //     boardBloc.groupControllers.values.first.group.desc == expectedGroupName,
+    //     "Expected $expectedGroupName, but receive ${boardBloc.groupControllers.values.first.group.desc}");
   });
 
   test('group by multi select with no options test', () async {
@@ -84,16 +84,16 @@ void main() {
     await boardResponseFuture();
 
     // assert there are only three group
-    final boardBloc = BoardBloc(view: context.gridView)
-      ..add(const BoardEvent.initial());
-    await boardResponseFuture();
-    assert(boardBloc.groupControllers.values.length == 3,
-        "Expected 3, but receive ${boardBloc.groupControllers.values.length}");
-
-    final groups =
-        boardBloc.groupControllers.values.map((e) => e.group).toList();
-    assert(groups[0].desc == "No ${multiSelectField.name}");
-    assert(groups[1].desc == "B");
-    assert(groups[2].desc == "A");
+    // final boardBloc = BoardBloc(view: context.gridView)
+    //   ..add(const BoardEvent.initial());
+    // await boardResponseFuture();
+    // assert(boardBloc.groupControllers.values.length == 3,
+    //     "Expected 3, but receive ${boardBloc.groupControllers.values.length}");
+
+    // final groups =
+    //     boardBloc.groupControllers.values.map((e) => e.group).toList();
+    // assert(groups[0].desc == "No ${multiSelectField.name}");
+    // assert(groups[1].desc == "B");
+    // assert(groups[2].desc == "A");
   });
 }

+ 2 - 0
frontend/appflowy_tauri/src-tauri/.cargo/config.toml

@@ -0,0 +1,2 @@
+[build]
+rustflags = ["--cfg", "tokio_unstable"]

+ 96 - 74
frontend/appflowy_tauri/src-tauri/Cargo.lock

@@ -106,19 +106,20 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
 
 [[package]]
 name = "async-stream"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
+checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e"
 dependencies = [
  "async-stream-impl",
  "futures-core",
+ "pin-project-lite",
 ]
 
 [[package]]
 name = "async-stream-impl"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
+checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -127,9 +128,9 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.61"
+version = "0.1.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282"
+checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -193,6 +194,12 @@ version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
+[[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
 [[package]]
 name = "bincode"
 version = "1.3.3"
@@ -249,19 +256,19 @@ dependencies = [
 
 [[package]]
 name = "borsh"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa"
+checksum = "40f9ca3698b2e4cb7c15571db0abc5551dca417a21ae8140460b50309bb2cc62"
 dependencies = [
  "borsh-derive",
- "hashbrown 0.11.2",
+ "hashbrown",
 ]
 
 [[package]]
 name = "borsh-derive"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775"
+checksum = "598b3eacc6db9c3ee57b22707ad8f6a8d2f6d442bfe24ffeb8cbb70ca59e6a35"
 dependencies = [
  "borsh-derive-internal",
  "borsh-schema-derive-internal",
@@ -272,9 +279,9 @@ dependencies = [
 
 [[package]]
 name = "borsh-derive-internal"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065"
+checksum = "186b734fa1c9f6743e90c95d7233c9faab6360d1a96d4ffa19d9cfd1e9350f8a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -283,9 +290,9 @@ dependencies = [
 
 [[package]]
 name = "borsh-schema-derive-internal"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0"
+checksum = "99b7ff1008316626f485991b960ade129253d4034014616b94f309a15366cc49"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -364,9 +371,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "bytes"
-version = "1.3.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
 dependencies = [
  "serde",
 ]
@@ -698,9 +705,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.14"
+version = "0.8.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
 dependencies = [
  "cfg-if",
 ]
@@ -844,7 +851,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
 dependencies = [
  "cfg-if",
- "hashbrown 0.12.3",
+ "hashbrown",
  "lock_api",
  "once_cell",
  "parking_lot_core",
@@ -1107,7 +1114,7 @@ dependencies = [
  "cfg-if",
  "libc",
  "redox_syscall 0.2.16",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -1630,9 +1637,9 @@ dependencies = [
 
 [[package]]
 name = "futures"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
+checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1645,9 +1652,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1655,15 +1662,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1672,15 +1679,15 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
+checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1689,21 +1696,21 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
 
 [[package]]
 name = "futures-task"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
 
 [[package]]
 name = "futures-util"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -2049,15 +2056,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "hashbrown"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
-dependencies = [
- "ahash",
-]
-
 [[package]]
 name = "hashbrown"
 version = "0.12.3"
@@ -2296,7 +2294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
 dependencies = [
  "autocfg",
- "hashbrown 0.12.3",
+ "hashbrown",
  "serde",
 ]
 
@@ -2745,7 +2743,7 @@ dependencies = [
  "libc",
  "log",
  "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -2936,9 +2934,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.17.0"
+version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
 
 [[package]]
 name = "opaque-debug"
@@ -2953,7 +2951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8"
 dependencies = [
  "pathdiff",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -3062,7 +3060,7 @@ dependencies = [
  "libc",
  "redox_syscall 0.2.16",
  "smallvec",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -3317,7 +3315,7 @@ version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225"
 dependencies = [
- "base64",
+ "base64 0.13.1",
  "indexmap",
  "line-wrap",
  "serde",
@@ -3401,9 +3399,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.49"
+version = "1.0.51"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
 dependencies = [
  "unicode-ident",
 ]
@@ -3719,11 +3717,11 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.11.13"
+version = "0.11.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
+checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
 dependencies = [
- "base64",
+ "base64 0.21.0",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -3771,7 +3769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15"
 dependencies = [
  "bytecheck",
- "hashbrown 0.12.3",
+ "hashbrown",
  "ptr_meta",
  "rend",
  "rkyv_derive",
@@ -3791,9 +3789,9 @@ dependencies = [
 
 [[package]]
 name = "rust_decimal"
-version = "1.27.0"
+version = "1.28.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33c321ee4e17d2b7abe12b5d20c1231db708dd36185c8a21e9de5fed6da4dbe9"
+checksum = "e13cf35f7140155d02ba4ec3294373d513a3c7baa8364c162b030e33c61520a8"
 dependencies = [
  "arrayvec 0.7.2",
  "borsh",
@@ -3884,7 +3882,7 @@ version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
 dependencies = [
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -4298,9 +4296,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.107"
+version = "1.0.109"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4459,7 +4457,7 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251"
 dependencies = [
- "base64",
+ "base64 0.13.1",
  "brotli",
  "ico",
  "json-patch",
@@ -4728,9 +4726,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.24.1"
+version = "1.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
+checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
 dependencies = [
  "autocfg",
  "bytes",
@@ -4743,7 +4741,7 @@ dependencies = [
  "signal-hook-registry",
  "socket2",
  "tokio-macros",
- "windows-sys",
+ "windows-sys 0.45.0",
 ]
 
 [[package]]
@@ -4796,9 +4794,9 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.5.10"
+version = "0.5.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
 dependencies = [
  "serde",
 ]
@@ -4953,7 +4951,7 @@ version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
 dependencies = [
- "base64",
+ "base64 0.13.1",
  "byteorder",
  "bytes",
  "http",
@@ -5459,6 +5457,30 @@ dependencies = [
  "windows_x86_64_msvc 0.42.1",
 ]
 
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc 0.42.1",
+ "windows_i686_gnu 0.42.1",
+ "windows_i686_msvc 0.42.1",
+ "windows_x86_64_gnu 0.42.1",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc 0.42.1",
+]
+
 [[package]]
 name = "windows-tokens"
 version = "0.39.0"
@@ -5561,7 +5583,7 @@ version = "0.23.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98"
 dependencies = [
- "base64",
+ "base64 0.13.1",
  "block",
  "cocoa",
  "core-graphics",

+ 1 - 1
frontend/appflowy_tauri/src-tauri/Cargo.toml

@@ -18,7 +18,7 @@ serde_json = "1.0"
 serde = { version = "1.0", features = ["derive"] }
 tauri = { version = "1.2", features = ["shell-open"] }
 tauri-utils = "1.2"
-bytes = { version = "1.0" }
+bytes = { version = "1.4" }
 tracing = { version = "0.1", features = ["log"] }
 lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = ["use_serde"] }
 flowy-core = { path = "../../rust-lib/flowy-core", features = ["rev-sqlite","ts"] }

+ 1 - 1
frontend/appflowy_tauri/src-tauri/build.rs

@@ -1,3 +1,3 @@
 fn main() {
-  tauri_build::build()
+    tauri_build::build()
 }

+ 16 - 15
frontend/appflowy_tauri/src-tauri/src/init.rs

@@ -1,21 +1,22 @@
 use flowy_core::{get_client_server_configuration, AppFlowyCore, AppFlowyCoreConfig};
 
 pub fn init_flowy_core() -> AppFlowyCore {
-  let config_json = include_str!("../tauri.conf.json");
-  let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap();
+    let config_json = include_str!("../tauri.conf.json");
+    let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap();
 
-  let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap();
-  if cfg!(debug_assertions) {
-    data_path.push("dev");
-  }
-  data_path.push("data");
+    let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap();
+    if cfg!(debug_assertions) {
+        data_path.push("dev");
+    }
+    data_path.push("data");
 
-  let server_config = get_client_server_configuration().unwrap();
-  let config = AppFlowyCoreConfig::new(
-    data_path.to_str().unwrap(),
-    "AppFlowy".to_string(),
-    server_config,
-  )
-  .log_filter("trace", vec!["appflowy_tauri".to_string()]);
-  AppFlowyCore::new(config)
+    std::env::set_var("RUST_LOG", "trace");
+    let server_config = get_client_server_configuration().unwrap();
+    let config = AppFlowyCoreConfig::new(
+        data_path.to_str().unwrap(),
+        "AppFlowy".to_string(),
+        server_config,
+    )
+        .log_filter("trace", vec!["appflowy_tauri".to_string()]);
+    AppFlowyCore::new(config)
 }

+ 0 - 1
frontend/appflowy_tauri/src-tauri/src/request.rs

@@ -32,7 +32,6 @@ impl std::convert::From<AFPluginEventResponse> for AFTauriResponse {
 }
 
 // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
-#[tracing::instrument(level = "trace", skip(app_handler))]
 #[tauri::command]
 pub async fn invoke_request(
   request: AFTauriRequest,

+ 2 - 3
frontend/appflowy_tauri/src-tauri/tauri.conf.json

@@ -60,11 +60,10 @@
     "windows": [
       {
         "fullscreen": false,
-        "height": 1000,
+        "height": 1200,
         "resizable": true,
         "title": "AppFlowy",
-        "width": 1200,
-        "transparent": true
+        "width": 1200
       }
     ]
   }

+ 2 - 0
frontend/appflowy_tauri/src/appflowy_app/App.tsx

@@ -14,6 +14,7 @@ import { ConfirmAccountPage } from './views/ConfirmAccountPage';
 import { ErrorHandlerPage } from './components/error/ErrorHandlerPage';
 import initializeI18n from './stores/i18n/initializeI18n';
 import { TestAPI } from './components/TestApiButton/TestAPI';
+import { GetStarted } from './components/auth/GetStarted/GetStarted';
 
 initializeI18n();
 
@@ -31,6 +32,7 @@ const App = () => {
             <Route path={'/'} element={<Welcome />} />
           </Route>
           <Route path={'/auth/login'} element={<LoginPage />}></Route>
+          <Route path={'/auth/getStarted'} element={<GetStarted />}></Route>
           <Route path={'/auth/signUp'} element={<SignUpPage />}></Route>
           <Route path={'/auth/confirm-account'} element={<ConfirmAccountPage />}></Route>
         </Routes>

+ 81 - 17
frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/DatabaseTestHelper.ts

@@ -11,8 +11,9 @@ import {
   SelectOptionCellController,
   TextCellController,
 } from '../../stores/effects/database/cell/controller_builder';
-import assert from 'assert';
 import { None, Option, Some } from 'ts-results';
+import { TypeOptionBackendService } from '../../stores/effects/database/field/type_option/type_option_bd_svc';
+import { DatabaseBackendService } from '../../stores/effects/database/database_bd_svc';
 
 // Create a database view for specific layout type
 // Do not use it production code. Just for testing
@@ -27,65 +28,95 @@ export async function openTestDatabase(viewId: string): Promise<DatabaseControll
   return new DatabaseController(viewId);
 }
 
-export async function assertTextCell(rowInfo: RowInfo, databaseController: DatabaseController, expectedContent: string) {
-  const cellController = await makeTextCellController(rowInfo, databaseController).then((result) => result.unwrap());
-  cellController.subscribeChanged({
+export async function assertTextCell(
+  fieldId: string,
+  rowInfo: RowInfo,
+  databaseController: DatabaseController,
+  expectedContent: string
+) {
+  const cellController = await makeTextCellController(fieldId, rowInfo, databaseController).then((result) =>
+    result.unwrap()
+  );
+  await cellController.subscribeChanged({
     onCellChanged: (value) => {
       const cellContent = value.unwrap();
       if (cellContent !== expectedContent) {
-        throw Error();
+        throw Error('Text cell content is not match');
       }
     },
   });
-  cellController.getCellData();
+  await cellController.getCellData();
 }
 
-export async function editTextCell(rowInfo: RowInfo, databaseController: DatabaseController, content: string) {
-  const cellController = await makeTextCellController(rowInfo, databaseController).then((result) => result.unwrap());
+export async function editTextCell(
+  fieldId: string,
+  rowInfo: RowInfo,
+  databaseController: DatabaseController,
+  content: string
+) {
+  const cellController = await makeTextCellController(fieldId, rowInfo, databaseController).then((result) =>
+    result.unwrap()
+  );
   await cellController.saveCellData(content);
 }
 
 export async function makeTextCellController(
+  fieldId: string,
   rowInfo: RowInfo,
   databaseController: DatabaseController
 ): Promise<Option<TextCellController>> {
-  const builder = await makeCellControllerBuilder(rowInfo, FieldType.RichText, databaseController).then((result) =>
-    result.unwrap()
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.RichText, databaseController).then(
+    (result) => result.unwrap()
   );
   return Some(builder.build() as TextCellController);
 }
 
 export async function makeNumberCellController(
+  fieldId: string,
   rowInfo: RowInfo,
   databaseController: DatabaseController
 ): Promise<Option<NumberCellController>> {
-  const builder = await makeCellControllerBuilder(rowInfo, FieldType.Number, databaseController).then((result) =>
-    result.unwrap()
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.Number, databaseController).then(
+    (result) => result.unwrap()
   );
   return Some(builder.build() as NumberCellController);
 }
 
 export async function makeSingleSelectCellController(
+  fieldId: string,
   rowInfo: RowInfo,
   databaseController: DatabaseController
 ): Promise<Option<SelectOptionCellController>> {
-  const builder = await makeCellControllerBuilder(rowInfo, FieldType.SingleSelect, databaseController).then((result) =>
-    result.unwrap()
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.SingleSelect, databaseController).then(
+    (result) => result.unwrap()
+  );
+  return Some(builder.build() as SelectOptionCellController);
+}
+
+export async function makeMultiSelectCellController(
+  fieldId: string,
+  rowInfo: RowInfo,
+  databaseController: DatabaseController
+): Promise<Option<SelectOptionCellController>> {
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.MultiSelect, databaseController).then(
+    (result) => result.unwrap()
   );
   return Some(builder.build() as SelectOptionCellController);
 }
 
 export async function makeDateCellController(
+  fieldId: string,
   rowInfo: RowInfo,
   databaseController: DatabaseController
 ): Promise<Option<DateCellController>> {
-  const builder = await makeCellControllerBuilder(rowInfo, FieldType.DateTime, databaseController).then((result) =>
-    result.unwrap()
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.DateTime, databaseController).then(
+    (result) => result.unwrap()
   );
   return Some(builder.build() as DateCellController);
 }
 
 export async function makeCellControllerBuilder(
+  fieldId: string,
   rowInfo: RowInfo,
   fieldType: FieldType,
   databaseController: DatabaseController
@@ -97,10 +128,43 @@ export async function makeCellControllerBuilder(
   const cellByFieldId = await rowController.loadCells();
   for (const cellIdentifier of cellByFieldId.values()) {
     const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
-    if (cellIdentifier.fieldType === fieldType) {
+    if (cellIdentifier.fieldId === fieldId) {
       return Some(builder);
     }
   }
 
   return None;
 }
+
+export function findFirstFieldInfoWithFieldType(rowInfo: RowInfo, fieldType: FieldType) {
+  const fieldInfo = rowInfo.fieldInfos.find((element) => element.field.field_type === fieldType);
+  if (fieldInfo === undefined) {
+    return None;
+  } else {
+    return Some(fieldInfo);
+  }
+}
+
+export async function assertFieldName(viewId: string, fieldId: string, fieldType: FieldType, expected: string) {
+  const svc = new TypeOptionBackendService(viewId);
+  const typeOptionPB = await svc.getTypeOption(fieldId, fieldType).then((result) => result.unwrap());
+  if (typeOptionPB.field.name !== expected) {
+    throw Error('Expect field name:' + expected + 'but receive:' + typeOptionPB.field.name);
+  }
+}
+
+export async function assertNumberOfFields(viewId: string, expected: number) {
+  const svc = new DatabaseBackendService(viewId);
+  const databasePB = await svc.openDatabase().then((result) => result.unwrap());
+  if (databasePB.fields.length !== expected) {
+    throw Error('Expect number of fields:' + expected + 'but receive:' + databasePB.fields.length);
+  }
+}
+
+export async function assertNumberOfRows(viewId: string, expected: number) {
+  const svc = new DatabaseBackendService(viewId);
+  const databasePB = await svc.openDatabase().then((result) => result.unwrap());
+  if (databasePB.rows.length !== expected) {
+    throw Error('Expect number of rows:' + expected + 'but receive:' + databasePB.rows.length);
+  }
+}

+ 22 - 2
frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/TestAPI.tsx

@@ -1,6 +1,18 @@
 import React from 'react';
 import TestApiButton from './TestApiButton';
-import { TestCreateGrid, TestCreateSelectOption, TestEditCell } from './TestGrid';
+import {
+  TestCreateGrid,
+  TestCreateNewField,
+  TestCreateRow,
+  TestCreateSelectOptionInCell,
+  TestDeleteField,
+  TestDeleteRow,
+  TestEditCell,
+  TestEditField,
+  TestGetSingleSelectFieldData,
+  TestSwitchFromMultiSelectToText,
+  TestSwitchFromSingleSelectToNumber,
+} from './TestGrid';
 
 export const TestAPI = () => {
   return (
@@ -8,8 +20,16 @@ export const TestAPI = () => {
       <ul className='m-6, space-y-2'>
         <TestApiButton></TestApiButton>
         <TestCreateGrid></TestCreateGrid>
+        <TestCreateRow></TestCreateRow>
+        <TestDeleteRow></TestDeleteRow>
         <TestEditCell></TestEditCell>
-        <TestCreateSelectOption></TestCreateSelectOption>
+        <TestCreateSelectOptionInCell></TestCreateSelectOptionInCell>
+        <TestGetSingleSelectFieldData></TestGetSingleSelectFieldData>
+        <TestEditField></TestEditField>
+        <TestCreateNewField></TestCreateNewField>
+        <TestDeleteField></TestDeleteField>
+        <TestSwitchFromSingleSelectToNumber></TestSwitchFromSingleSelectToNumber>
+        <TestSwitchFromMultiSelectToText></TestSwitchFromMultiSelectToText>
       </ul>
     </React.Fragment>
   );

+ 277 - 38
frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/TestGrid.tsx

@@ -1,15 +1,37 @@
 import React from 'react';
-import { SelectOptionCellDataPB, ViewLayoutTypePB } from '../../../services/backend';
+import {
+  FieldType,
+  NumberFormat,
+  NumberTypeOptionPB,
+  SelectOptionCellDataPB,
+  SingleSelectTypeOptionPB,
+  ViewLayoutTypePB,
+} from '../../../services/backend';
 import { Log } from '../../utils/log';
 import {
+  assertFieldName,
+  assertNumberOfFields,
+  assertNumberOfRows,
   assertTextCell,
   createTestDatabaseView,
   editTextCell,
+  findFirstFieldInfoWithFieldType,
+  makeMultiSelectCellController,
   makeSingleSelectCellController,
+  makeTextCellController,
   openTestDatabase,
 } from './DatabaseTestHelper';
-import assert from 'assert';
-import { SelectOptionBackendService } from '../../stores/effects/database/cell/select_option_bd_svc';
+import {
+  SelectOptionBackendService,
+  SelectOptionCellBackendService,
+} from '../../stores/effects/database/cell/select_option_bd_svc';
+import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
+import { None, Some } from 'ts-results';
+import { RowBackendService } from '../../stores/effects/database/row/row_bd_svc';
+import {
+  makeNumberTypeOptionContext,
+  makeSingleSelectTypeOptionContext,
+} from '../../stores/effects/database/field/type_option/type_option_context';
 
 export const TestCreateGrid = () => {
   async function createBuildInGrid() {
@@ -19,14 +41,19 @@ export const TestCreateGrid = () => {
       onViewChanged: (databasePB) => {
         Log.debug('Did receive database:' + databasePB);
       },
-      onRowsChanged: async (rows) => {
-        assert(rows.length === 3);
-      },
+      // onRowsChanged: async (rows) => {
+      //   if (rows.length !== 3) {
+      //     throw Error('Expected number of rows is 3, but receive ' + rows.length);
+      //   }
+      // },
       onFieldsChanged: (fields) => {
-        assert(fields.length === 3);
+        if (fields.length !== 3) {
+          throw Error('Expected number of fields is 3, but receive ' + fields.length);
+        }
       },
     });
     await databaseController.open().then((result) => result.unwrap());
+    await databaseController.dispose();
   }
 
   return TestButton('Test create build-in grid', createBuildInGrid);
@@ -36,48 +63,260 @@ export const TestEditCell = () => {
   async function testGridRow() {
     const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
     const databaseController = await openTestDatabase(view.id);
-    databaseController.subscribe({
-      onRowsChanged: async (rows) => {
-        for (const [index, row] of rows.entries()) {
-          const cellContent = index.toString();
-          await editTextCell(row, databaseController, cellContent);
-          await assertTextCell(row, databaseController, cellContent);
-        }
-      },
-    });
     await databaseController.open().then((result) => result.unwrap());
+
+    for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
+      const cellContent = index.toString();
+      const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.RichText).unwrap();
+      await editTextCell(fieldInfo.field.id, row, databaseController, cellContent);
+      await assertTextCell(fieldInfo.field.id, row, databaseController, cellContent);
+    }
   }
 
   return TestButton('Test editing cell', testGridRow);
 };
 
-export const TestCreateSelectOption = () => {
-  async function testCreateOption() {
+export const TestCreateRow = () => {
+  async function testCreateRow() {
     const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
     const databaseController = await openTestDatabase(view.id);
-    databaseController.subscribe({
-      onRowsChanged: async (rows) => {
-        for (const [index, row] of rows.entries()) {
-          if (index === 0) {
-            const cellController = await makeSingleSelectCellController(row, databaseController).then((result) =>
-              result.unwrap()
-            );
-            cellController.subscribeChanged({
-              onCellChanged: (value) => {
-                const option: SelectOptionCellDataPB = value.unwrap();
-                console.log(option);
-              },
-            });
-            const backendSvc = new SelectOptionBackendService(cellController.cellIdentifier);
-            await backendSvc.createOption({ name: 'option' + index });
-          }
-        }
-      },
-    });
     await databaseController.open().then((result) => result.unwrap());
+    await assertNumberOfRows(view.id, 3);
+
+    // Create a row from a DatabaseController or create using the RowBackendService
+    await databaseController.createRow();
+    await assertNumberOfRows(view.id, 4);
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test create row', testCreateRow);
+};
+export const TestDeleteRow = () => {
+  async function testDeleteRow() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+
+    const rows = databaseController.databaseViewCache.rowInfos;
+    const svc = new RowBackendService(view.id);
+    await svc.deleteRow(rows[0].row.id);
+    await assertNumberOfRows(view.id, 2);
+
+    // Wait the databaseViewCache get the change notification and
+    // update the rows.
+    await new Promise((resolve) => setTimeout(resolve, 200));
+    if (databaseController.databaseViewCache.rowInfos.length !== 2) {
+      throw Error('The number of rows is not match');
+    }
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test delete row', testDeleteRow);
+};
+export const TestCreateSelectOptionInCell = () => {
+  async function testCreateOptionInCell() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+    for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
+      if (index === 0) {
+        const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.SingleSelect).unwrap();
+        const cellController = await makeSingleSelectCellController(fieldInfo.field.id, row, databaseController).then(
+          (result) => result.unwrap()
+        );
+        await cellController.subscribeChanged({
+          onCellChanged: (value) => {
+            const option: SelectOptionCellDataPB = value.unwrap();
+            console.log(option);
+          },
+        });
+        const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);
+        await backendSvc.createOption({ name: 'option' + index });
+        await cellController.dispose();
+      }
+    }
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test create a select option in cell', testCreateOptionInCell);
+};
+
+export const TestGetSingleSelectFieldData = () => {
+  async function testGetSingleSelectFieldData() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+
+    // Find the single select column
+    const singleSelect = databaseController.fieldController.fieldInfos.find(
+      (fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
+    )!;
+    const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
+    const singleSelectTypeOptionContext = makeSingleSelectTypeOptionContext(typeOptionController);
+
+    // Create options
+    const singleSelectTypeOptionPB: SingleSelectTypeOptionPB = await singleSelectTypeOptionContext
+      .getTypeOption()
+      .then((result) => result.unwrap());
+    const backendSvc = new SelectOptionBackendService(view.id, singleSelect.field.id);
+    const option1 = await backendSvc.createOption({ name: 'Task 1' }).then((result) => result.unwrap());
+    singleSelectTypeOptionPB.options.splice(0, 0, option1);
+    const option2 = await backendSvc.createOption({ name: 'Task 2' }).then((result) => result.unwrap());
+    singleSelectTypeOptionPB.options.splice(0, 0, option2);
+    const option3 = await backendSvc.createOption({ name: 'Task 3' }).then((result) => result.unwrap());
+    singleSelectTypeOptionPB.options.splice(0, 0, option3);
+    await singleSelectTypeOptionContext.setTypeOption(singleSelectTypeOptionPB);
+
+    // Read options
+    const options = singleSelectTypeOptionPB.options;
+    console.log(options);
+
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test get single-select column data', testGetSingleSelectFieldData);
+};
+
+export const TestSwitchFromSingleSelectToNumber = () => {
+  async function testSwitchFromSingleSelectToNumber() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+
+    // Find the single select column
+    const singleSelect = databaseController.fieldController.fieldInfos.find(
+      (fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
+    )!;
+    const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
+    await typeOptionController.switchToField(FieldType.Number);
+
+    // Check the number type option
+    const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
+    const numberTypeOption: NumberTypeOptionPB = await numberTypeOptionContext
+      .getTypeOption()
+      .then((result) => result.unwrap());
+    const format: NumberFormat = numberTypeOption.format;
+    if (format !== NumberFormat.Num) {
+      throw Error('The default format should be number');
+    }
+
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test switch from single-select to number column', testSwitchFromSingleSelectToNumber);
+};
+
+export const TestSwitchFromMultiSelectToText = () => {
+  async function testSwitchFromMultiSelectToRichText() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+
+    // Create multi-select field
+    const typeOptionController = new TypeOptionController(view.id, None, FieldType.MultiSelect);
+    await typeOptionController.initialize();
+
+    // Insert options to first row
+    const row = databaseController.databaseViewCache.rowInfos[0];
+    const multiSelectField = typeOptionController.getFieldInfo();
+    // const multiSelectField = findFirstFieldInfoWithFieldType(row, FieldType.MultiSelect).unwrap();
+    const selectOptionCellController = await makeMultiSelectCellController(
+      multiSelectField.field.id,
+      row,
+      databaseController
+    ).then((result) => result.unwrap());
+    const backendSvc = new SelectOptionCellBackendService(selectOptionCellController.cellIdentifier);
+    await backendSvc.createOption({ name: 'A' });
+    await backendSvc.createOption({ name: 'B' });
+    await backendSvc.createOption({ name: 'C' });
+
+    const selectOptionCellData = await selectOptionCellController.getCellData().then((result) => result.unwrap());
+    if (selectOptionCellData.options.length !== 3) {
+      throw Error('The options should equal to 3');
+    }
+
+    if (selectOptionCellData.select_options.length !== 3) {
+      throw Error('The selected options should equal to 3');
+    }
+    await selectOptionCellController.dispose();
+
+    // Switch to RichText field type
+    await typeOptionController.switchToField(FieldType.RichText).then((result) => result.unwrap());
+    if (typeOptionController.fieldType !== FieldType.RichText) {
+      throw Error('The field type should be text');
+    }
+
+    const textCellController = await makeTextCellController(multiSelectField.field.id, row, databaseController).then(
+      (result) => result.unwrap()
+    );
+    const cellContent = await textCellController.getCellData();
+    if (cellContent.unwrap() !== 'A,B,C') {
+      throw Error('The cell content should be A,B,C, but receive: ' + cellContent.unwrap());
+    }
+
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test switch from multi-select to text column', testSwitchFromMultiSelectToRichText);
+};
+
+export const TestEditField = () => {
+  async function testEditField() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+    const fieldInfos = databaseController.fieldController.fieldInfos;
+
+    // Modify the name of the field
+    const firstFieldInfo = fieldInfos[0];
+    const controller = new TypeOptionController(view.id, Some(firstFieldInfo));
+    await controller.initialize();
+    const newName = 'hello world';
+    await controller.setFieldName(newName);
+
+    await assertFieldName(view.id, firstFieldInfo.field.id, firstFieldInfo.field.field_type, newName);
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test edit the column name', testEditField);
+};
+
+export const TestCreateNewField = () => {
+  async function testCreateNewField() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+    await assertNumberOfFields(view.id, 3);
+
+    // Modify the name of the field
+    const controller = new TypeOptionController(view.id, None);
+    await controller.initialize();
+    await assertNumberOfFields(view.id, 4);
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test create a new column', testCreateNewField);
+};
+
+export const TestDeleteField = () => {
+  async function testDeleteField() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+
+    // Modify the name of the field.
+    // The fieldInfos[0] is the primary field by default, we can't delete it.
+    // So let choose the second fieldInfo.
+    const fieldInfo = databaseController.fieldController.fieldInfos[1];
+    const controller = new TypeOptionController(view.id, Some(fieldInfo));
+    await controller.initialize();
+    await assertNumberOfFields(view.id, 3);
+    await controller.deleteField();
+    await assertNumberOfFields(view.id, 2);
+    await databaseController.dispose();
   }
 
-  return TestButton('Test create a select option', testCreateOption);
+  return TestButton('Test delete a new column', testDeleteField);
 };
 
 const TestButton = (title: string, onClick: () => void) => {

+ 31 - 0
frontend/appflowy_tauri/src/appflowy_app/components/auth/GetStarted/GetStarted.tsx

@@ -0,0 +1,31 @@
+import { t } from 'i18next';
+import { AppflowyLogo } from '../../_shared/svg/AppflowyLogo';
+import { Button } from '../../_shared/Button';
+import { useLogin } from '../Login/Login.hooks';
+
+export const GetStarted = () => {
+  const { onAutoSignInClick } = useLogin();
+  return (
+    <>
+      <form onSubmit={(e) => e.preventDefault()} method='POST'>
+        <div className='relative flex h-screen w-screen flex-col items-center justify-center gap-12 text-center'>
+          <div className='flex h-10 w-10 justify-center'>
+            <AppflowyLogo />
+          </div>
+
+          <div>
+            <span className='text-2xl font-semibold leading-9'>
+              {t('signIn.loginTitle').replace('@:appName', 'AppFlowy')}
+            </span>
+          </div>
+
+          <div className='flex w-full max-w-[340px] flex-col gap-6 '>
+            <Button size={'primary'} onClick={() => onAutoSignInClick()}>
+              {t('signUp.getStartedText')}
+            </Button>
+          </div>
+        </div>
+      </form>
+    </>
+  );
+};

+ 26 - 3
frontend/appflowy_tauri/src/appflowy_app/components/auth/Login/Login.hooks.ts

@@ -3,6 +3,7 @@ import { currentUserActions } from '../../../stores/reducers/current-user/slice'
 import { useAppDispatch, useAppSelector } from '../../../stores/store';
 import { useNavigate } from 'react-router-dom';
 import { useAuth } from '../auth.hooks';
+import { nanoid } from 'nanoid';
 
 export const useLogin = () => {
   const [email, setEmail] = useState('');
@@ -11,7 +12,7 @@ export const useLogin = () => {
   const appDispatch = useAppDispatch();
   const currentUser = useAppSelector((state) => state.currentUser);
   const navigate = useNavigate();
-  const { login } = useAuth();
+  const { login, register } = useAuth();
   const [authError, setAuthError] = useState(false);
 
   function onTogglePassword() {
@@ -29,10 +30,31 @@ export const useLogin = () => {
     setPassword(v);
   }
 
+  async function onAutoSignInClick() {
+    try {
+      const fakeEmail = nanoid(8) + '@appflowy.io';
+      const fakePassword = 'AppFlowy123@';
+      const userProfile = await register(fakeEmail, fakePassword, 'Me');
+      const { id, name, token } = userProfile;
+      appDispatch(
+        currentUserActions.updateUser({
+          id: id,
+          displayName: name,
+          email: email,
+          token: token,
+          isAuthenticated: true,
+        })
+      );
+      navigate('/');
+    } catch (e) {
+      setAuthError(true);
+    }
+  }
+
   async function onSignInClick() {
     try {
-      const result = await login(email, password);
-      const { id, name, token } = result;
+      const userProfile = await login(email, password);
+      const { id, name, token } = userProfile;
       appDispatch(
         currentUserActions.updateUser({
           id: id,
@@ -52,6 +74,7 @@ export const useLogin = () => {
     showPassword,
     onTogglePassword,
     onSignInClick,
+    onAutoSignInClick,
     email,
     setEmail: _setEmail,
     password,

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx

@@ -1,6 +1,6 @@
 import { Navigate, Outlet, useLocation } from 'react-router-dom';
 import { useAuth } from './auth.hooks';
-import { Screen } from '../../components/layout/Screen';
+import { Screen } from '../layout/Screen';
 
 export const ProtectedRoutes = () => {
   const location = useLocation();
@@ -11,6 +11,6 @@ export const ProtectedRoutes = () => {
       <Outlet />
     </Screen>
   ) : (
-    <Navigate to='/auth/login' replace state={{ from: location }} />
+    <Navigate to='/auth/getStarted' replace state={{ from: location }} />
   );
 };

+ 23 - 17
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_cache.ts

@@ -5,43 +5,49 @@ export class CellCacheKey {
   constructor(public readonly fieldId: string, public readonly rowId: string) {}
 }
 
+type CellDataByRowId = Map<string, any>;
+
 export class CellCache {
-  _cellDataByFieldId = new Map<string, Map<string, any>>();
+  private cellDataByFieldId = new Map<string, CellDataByRowId>();
 
   constructor(public readonly databaseId: string) {}
 
   remove = (key: CellCacheKey) => {
-    const inner = this._cellDataByFieldId.get(key.fieldId);
-    if (inner !== undefined) {
-      inner.delete(key.rowId);
+    const cellDataByRowId = this.cellDataByFieldId.get(key.fieldId);
+    if (cellDataByRowId !== undefined) {
+      cellDataByRowId.delete(key.rowId);
     }
   };
 
   removeWithFieldId = (fieldId: string) => {
-    this._cellDataByFieldId.delete(fieldId);
+    this.cellDataByFieldId.delete(fieldId);
   };
 
   insert = (key: CellCacheKey, value: any) => {
-    let inner = this._cellDataByFieldId.get(key.fieldId);
-    if (inner === undefined) {
-      inner = this._cellDataByFieldId.set(key.fieldId, new Map());
+    const cellDataByRowId = this.cellDataByFieldId.get(key.fieldId);
+    if (cellDataByRowId === undefined) {
+      const map = new Map();
+      map.set(key.rowId, value);
+      this.cellDataByFieldId.set(key.fieldId, map);
+    } else {
+      cellDataByRowId.set(key.rowId, value);
     }
-    inner.set(key.rowId, value);
   };
 
   get<T>(key: CellCacheKey): Option<T> {
-    const inner = this._cellDataByFieldId.get(key.fieldId);
-    if (inner === undefined) {
+    const cellDataByRowId = this.cellDataByFieldId.get(key.fieldId);
+    if (cellDataByRowId === undefined) {
       return None;
     } else {
-      const value = inner.get(key.rowId);
-      if (typeof value === typeof undefined || typeof value === typeof null) {
+      const value = cellDataByRowId.get(key.rowId);
+      if (typeof value === typeof undefined) {
         return None;
       }
-      if (value satisfies T) {
-        return Some(value as T);
-      }
-      return None;
+
+      // if (value satisfies T) {
+      //   return Some(value as T);
+      // }
+      return Some(value);
     }
   }
 }

+ 54 - 54
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_controller.ts

@@ -1,6 +1,5 @@
 import { CellIdentifier } from './cell_bd_svc';
 import { CellCache, CellCacheKey } from './cell_cache';
-import { FieldController } from '../field/field_controller';
 import { CellDataLoader } from './data_parser';
 import { CellDataPersistence } from './data_persistence';
 import { FieldBackendService, TypeOptionParser } from '../field/field_bd_svc';
@@ -8,54 +7,57 @@ import { ChangeNotifier } from '../../../../utils/change_notifier';
 import { CellObserver } from './cell_observer';
 import { Log } from '../../../../utils/log';
 import { Err, None, Ok, Option, Some } from 'ts-results';
+import { DatabaseFieldObserver } from '../field/field_observer';
 
-export abstract class CellFieldNotifier {
-  abstract subscribeOnFieldChanged(callback: () => void): void;
-}
+type Callbacks<T> = { onCellChanged: (value: Option<T>) => void; onFieldChanged?: () => void };
 
 export class CellController<T, D> {
-  private _fieldBackendService: FieldBackendService;
-  private _cellDataNotifier: CellDataNotifier<Option<T>>;
-  private _cellObserver: CellObserver;
-  private _cacheKey: CellCacheKey;
+  private fieldBackendService: FieldBackendService;
+  private cellDataNotifier: CellDataNotifier<Option<T>>;
+  private cellObserver: CellObserver;
+  private readonly cacheKey: CellCacheKey;
+  private readonly fieldNotifier: DatabaseFieldObserver;
+  private subscribeCallbacks?: Callbacks<T>;
 
   constructor(
     public readonly cellIdentifier: CellIdentifier,
     private readonly cellCache: CellCache,
-    private readonly fieldNotifier: CellFieldNotifier,
     private readonly cellDataLoader: CellDataLoader<T>,
     private readonly cellDataPersistence: CellDataPersistence<D>
   ) {
-    this._fieldBackendService = new FieldBackendService(cellIdentifier.viewId, cellIdentifier.fieldId);
-    this._cacheKey = new CellCacheKey(cellIdentifier.rowId, cellIdentifier.fieldId);
-    this._cellDataNotifier = new CellDataNotifier(cellCache.get<T>(this._cacheKey));
-    this._cellObserver = new CellObserver(cellIdentifier.rowId, cellIdentifier.fieldId);
-    this._cellObserver.subscribe({
+    this.fieldBackendService = new FieldBackendService(cellIdentifier.viewId, cellIdentifier.fieldId);
+    this.cacheKey = new CellCacheKey(cellIdentifier.fieldId, cellIdentifier.rowId);
+    this.cellDataNotifier = new CellDataNotifier(cellCache.get<T>(this.cacheKey));
+    this.cellObserver = new CellObserver(cellIdentifier.rowId, cellIdentifier.fieldId);
+    this.fieldNotifier = new DatabaseFieldObserver(cellIdentifier.fieldId);
+    void this.cellObserver.subscribe({
       /// 1.Listen on user edit event and load the new cell data if needed.
       /// For example:
       ///  user input: 12
       ///  cell display: $12
       onCellChanged: async () => {
-        this.cellCache.remove(this._cacheKey);
+        this.cellCache.remove(this.cacheKey);
         await this._loadCellData();
       },
     });
-  }
 
-  subscribeChanged = (callbacks: { onCellChanged: (value: Option<T>) => void; onFieldChanged?: () => void }) => {
     /// 2.Listen on the field event and load the cell data if needed.
-    this.fieldNotifier.subscribeOnFieldChanged(async () => {
-      callbacks.onFieldChanged?.();
-
-      /// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
-      /// For example:
-      ///   ¥12 -> $12
-      if (this.cellDataLoader.reloadOnFieldChanged) {
-        await this._loadCellData();
-      }
+    void this.fieldNotifier.subscribe({
+      onFieldChanged: () => {
+        this.subscribeCallbacks?.onFieldChanged?.();
+        /// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
+        /// For example:
+        ///   ¥12 -> $12
+        if (this.cellDataLoader.reloadOnFieldChanged) {
+          void this._loadCellData();
+        }
+      },
     });
+  }
 
-    this._cellDataNotifier.observer.subscribe((cellData) => {
+  subscribeChanged = async (callbacks: Callbacks<T>) => {
+    this.subscribeCallbacks = callbacks;
+    this.cellDataNotifier.observer.subscribe((cellData) => {
       if (cellData !== null) {
         callbacks.onCellChanged(cellData);
       }
@@ -63,7 +65,7 @@ export class CellController<T, D> {
   };
 
   getTypeOption = async <P extends TypeOptionParser<PD>, PD>(parser: P) => {
-    const result = await this._fieldBackendService.getTypeOptionData(this.cellIdentifier.fieldType);
+    const result = await this.fieldBackendService.getTypeOptionData(this.cellIdentifier.fieldType);
     if (result.ok) {
       return Ok(parser.fromBuffer(result.val.type_option_data));
     } else {
@@ -78,57 +80,55 @@ export class CellController<T, D> {
     }
   };
 
-  /// Return the cell data if it exists in the cache
-  /// If the cell data is not exist, it will load the cell
-  /// data from the backend and then the [onCellChanged] will
-  /// get called
-  getCellData = (): Option<T> => {
-    const cellData = this.cellCache.get<T>(this._cacheKey);
+  /// Return the cell data immediately if it exists in the cache
+  /// Otherwise, it will load the cell data from the backend. The
+  /// subscribers of the [onCellChanged] will get noticed
+  getCellData = async (): Promise<Option<T>> => {
+    const cellData = this.cellCache.get<T>(this.cacheKey);
     if (cellData.none) {
-      void this._loadCellData();
+      await this._loadCellData();
+      return this.cellCache.get<T>(this.cacheKey);
     }
     return cellData;
   };
 
   private _loadCellData = () => {
     return this.cellDataLoader.loadData().then((result) => {
-      if (result.ok && result.val !== undefined) {
-        this.cellCache.insert(this._cacheKey, result.val);
-        this._cellDataNotifier.cellData = Some(result.val);
+      if (result.ok) {
+        this.cellCache.insert(this.cacheKey, result.val);
+        this.cellDataNotifier.cellData = Some(result.val);
       } else {
-        this.cellCache.remove(this._cacheKey);
-        this._cellDataNotifier.cellData = None;
+        this.cellCache.remove(this.cacheKey);
+        this.cellDataNotifier.cellData = None;
       }
     });
   };
-}
-
-export class CellFieldNotifierImpl extends CellFieldNotifier {
-  constructor(private readonly fieldController: FieldController) {
-    super();
-  }
 
-  subscribeOnFieldChanged(callback: () => void): void {
-    this.fieldController.subscribeOnFieldsChanged(callback);
-  }
+  dispose = async () => {
+    await this.cellObserver.unsubscribe();
+    await this.fieldNotifier.unsubscribe();
+  };
 }
 
 class CellDataNotifier<T> extends ChangeNotifier<T | null> {
-  _cellData: T | null;
+  _cellData: Option<T>;
 
   constructor(cellData: T) {
     super();
-    this._cellData = cellData;
+    this._cellData = Some(cellData);
   }
 
-  set cellData(data: T | null) {
+  set cellData(data: Option<T>) {
     if (this._cellData !== data) {
       this._cellData = data;
-      this.notify(this._cellData);
+
+      if (this._cellData.some) {
+        this.notify(this._cellData.val);
+      }
     }
   }
 
-  get cellData(): T | null {
+  get cellData(): Option<T> {
     return this._cellData;
   }
 }

+ 15 - 12
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_observer.ts

@@ -8,24 +8,27 @@ type UpdateCellNotifiedValue = Result<void, FlowyError>;
 export type CellChangedCallback = (value: UpdateCellNotifiedValue) => void;
 
 export class CellObserver {
-  private _notifier?: ChangeNotifier<UpdateCellNotifiedValue>;
-  private _listener?: DatabaseNotificationObserver;
+  private notifier?: ChangeNotifier<UpdateCellNotifiedValue>;
+  private listener?: DatabaseNotificationObserver;
 
   constructor(public readonly rowId: string, public readonly fieldId: string) {}
 
-  subscribe = (callbacks: { onCellChanged: CellChangedCallback }) => {
-    this._notifier = new ChangeNotifier();
-    this._notifier?.observer.subscribe(callbacks.onCellChanged);
+  subscribe = async (callbacks: { onCellChanged: CellChangedCallback }) => {
+    this.notifier = new ChangeNotifier();
+    this.notifier?.observer.subscribe(callbacks.onCellChanged);
 
-    this._listener = new DatabaseNotificationObserver({
-      viewId: this.rowId + ':' + this.fieldId,
+    this.listener = new DatabaseNotificationObserver({
+      // The rowId combine with fieldId can identifier the cell.
+      // This format rowId:fieldId is also defined in the backend,
+      // so don't change this.
+      id: this.rowId + ':' + this.fieldId,
       parserHandler: (notification, result) => {
         switch (notification) {
           case DatabaseNotification.DidUpdateCell:
             if (result.ok) {
-              this._notifier?.notify(Ok.EMPTY);
+              this.notifier?.notify(Ok.EMPTY);
             } else {
-              this._notifier?.notify(result);
+              this.notifier?.notify(result);
             }
             return;
           default:
@@ -33,11 +36,11 @@ export class CellObserver {
         }
       },
     });
-    return undefined;
+    await this.listener.start();
   };
 
   unsubscribe = async () => {
-    this._notifier?.unsubscribe();
-    await this._listener?.stop();
+    this.notifier?.unsubscribe();
+    await this.listener?.stop();
   };
 }

+ 9 - 54
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/controller_builder.ts

@@ -1,11 +1,6 @@
-import {
-  DateCellDataPB,
-  FieldType,
-  SelectOptionCellDataPB,
-  URLCellDataPB,
-} from '../../../../../services/backend/models/flowy-database';
+import { DateCellDataPB, FieldType, SelectOptionCellDataPB, URLCellDataPB } from '../../../../../services/backend';
 import { CellIdentifier } from './cell_bd_svc';
-import { CellController, CellFieldNotifierImpl } from './cell_controller';
+import { CellController } from './cell_controller';
 import {
   CellDataLoader,
   DateCellDataParser,
@@ -34,15 +29,11 @@ export class CalendarData {
 export type URLCellController = CellController<URLCellDataPB, string>;
 
 export class CellControllerBuilder {
-  _fieldNotifier: CellFieldNotifierImpl;
-
   constructor(
     public readonly cellIdentifier: CellIdentifier,
     public readonly cellCache: CellCache,
     public readonly fieldController: FieldController
-  ) {
-    this._fieldNotifier = new CellFieldNotifierImpl(this.fieldController);
-  }
+  ) {}
 
   ///
   build = () => {
@@ -68,77 +59,41 @@ export class CellControllerBuilder {
     const loader = new CellDataLoader(this.cellIdentifier, new SelectOptionCellDataParser(), true);
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<SelectOptionCellDataPB, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<SelectOptionCellDataPB, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeURLCellController = (): URLCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new URLCellDataParser());
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<URLCellDataPB, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<URLCellDataPB, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeDateCellController = (): DateCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new DateCellDataParser(), true);
     const persistence = new DateCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<DateCellDataPB, CalendarData>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<DateCellDataPB, CalendarData>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeNumberCellController = (): NumberCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new StringCellDataParser(), true);
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<string, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<string, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeTextCellController = (): TextCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new StringCellDataParser());
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<string, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<string, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeCheckboxCellController = (): CheckboxCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new StringCellDataParser());
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<string, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<string, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 }

+ 18 - 17
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_parser.ts

@@ -3,15 +3,15 @@ import { CellBackendService, CellIdentifier } from './cell_bd_svc';
 import { DateCellDataPB } from '../../../../../services/backend/models/flowy-database/date_type_option_entities';
 import { SelectOptionCellDataPB } from '../../../../../services/backend/models/flowy-database/select_type_option';
 import { URLCellDataPB } from '../../../../../services/backend/models/flowy-database/url_type_option_entities';
-import { Err, Ok } from 'ts-results';
+import { Err, None, Ok, Option, Some } from 'ts-results';
 import { Log } from '../../../../utils/log';
 
 abstract class CellDataParser<T> {
-  abstract parserData(data: Uint8Array): T | undefined;
+  abstract parserData(data: Uint8Array): Option<T>;
 }
 
 class CellDataLoader<T> {
-  _service = new CellBackendService();
+  private service = new CellBackendService();
 
   constructor(
     readonly cellId: CellIdentifier,
@@ -20,7 +20,7 @@ class CellDataLoader<T> {
   ) {}
 
   loadData = async () => {
-    const result = await this._service.getCell(this.cellId);
+    const result = await this.service.getCell(this.cellId);
     if (result.ok) {
       return Ok(this.parser.parserData(result.val.data));
     } else {
@@ -30,35 +30,36 @@ class CellDataLoader<T> {
   };
 }
 
-const utf8Decoder = new TextDecoder('utf-8');
+export const utf8Decoder = new TextDecoder('utf-8');
+export const utf8Encoder = new TextEncoder();
 
 class StringCellDataParser extends CellDataParser<string> {
-  parserData(data: Uint8Array): string {
-    return utf8Decoder.decode(data);
+  parserData(data: Uint8Array): Option<string> {
+    return Some(utf8Decoder.decode(data));
   }
 }
 
 class DateCellDataParser extends CellDataParser<DateCellDataPB> {
-  parserData(data: Uint8Array): DateCellDataPB {
-    return DateCellDataPB.deserializeBinary(data);
+  parserData(data: Uint8Array): Option<DateCellDataPB> {
+    return Some(DateCellDataPB.deserializeBinary(data));
   }
 }
 
-class SelectOptionCellDataParser extends CellDataParser<SelectOptionCellDataPB | undefined> {
-  parserData(data: Uint8Array): SelectOptionCellDataPB | undefined {
+class SelectOptionCellDataParser extends CellDataParser<SelectOptionCellDataPB> {
+  parserData(data: Uint8Array): Option<SelectOptionCellDataPB> {
     if (data.length === 0) {
-      return undefined;
+      return None;
     }
-    return SelectOptionCellDataPB.deserializeBinary(data);
+    return Some(SelectOptionCellDataPB.deserializeBinary(data));
   }
 }
 
-class URLCellDataParser extends CellDataParser<URLCellDataPB | undefined> {
-  parserData(data: Uint8Array): URLCellDataPB | undefined {
+class URLCellDataParser extends CellDataParser<URLCellDataPB> {
+  parserData(data: Uint8Array): Option<URLCellDataPB> {
     if (data.length === 0) {
-      return undefined;
+      return None;
     }
-    return URLCellDataPB.deserializeBinary(data);
+    return Some(URLCellDataPB.deserializeBinary(data));
   }
 }
 

+ 0 - 2
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_persistence.ts

@@ -27,10 +27,8 @@ export class DateCellDataPersistence extends CellDataPersistence<CalendarData> {
 
   save(data: CalendarData): Promise<Result<void, FlowyError>> {
     const payload = DateChangesetPB.fromObject({ cell_path: _makeCellPath(this.cellIdentifier) });
-
     payload.date = data.date.getUTCMilliseconds.toString();
     payload.is_utc = true;
-
     if (data.time !== undefined) {
       payload.time = data.time;
     }

+ 26 - 11
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/select_option_bd_svc.ts

@@ -14,8 +14,23 @@ import {
 } from '../../../../../services/backend/events/flowy-database';
 
 export class SelectOptionBackendService {
+  constructor(public readonly viewId: string, public readonly fieldId: string) {}
+
+  createOption = async (params: { name: string }) => {
+    const payload = CreateSelectOptionPayloadPB.fromObject({
+      option_name: params.name,
+      view_id: this.viewId,
+      field_id: this.fieldId,
+    });
+
+    return DatabaseEventCreateSelectOption(payload);
+  };
+}
+
+export class SelectOptionCellBackendService {
   constructor(public readonly cellIdentifier: CellIdentifier) {}
 
+  // Creates a new option and insert this option to the cell
   createOption = async (params: { name: string; isSelect?: boolean }) => {
     const payload = CreateSelectOptionPayloadPB.fromObject({
       option_name: params.name,
@@ -25,12 +40,22 @@ export class SelectOptionBackendService {
 
     const result = await DatabaseEventCreateSelectOption(payload);
     if (result.ok) {
-      return this._insertOption(result.val, params.isSelect || true);
+      return await this._insertOption(result.val, params.isSelect || true);
     } else {
       return result;
     }
   };
 
+  private _insertOption = (option: SelectOptionPB, isSelect: boolean) => {
+    const payload = SelectOptionChangesetPB.fromObject({ cell_identifier: this._cellIdentifier() });
+    if (isSelect) {
+      payload.insert_options.push(option);
+    } else {
+      payload.update_options.push(option);
+    }
+    return DatabaseEventUpdateSelectOption(payload);
+  };
+
   updateOption = (option: SelectOptionPB) => {
     const payload = SelectOptionChangesetPB.fromObject({ cell_identifier: this._cellIdentifier() });
     payload.update_options.push(option);
@@ -59,16 +84,6 @@ export class SelectOptionBackendService {
     return DatabaseEventUpdateSelectOptionCell(payload);
   };
 
-  private _insertOption = (option: SelectOptionPB, isSelect: boolean) => {
-    const payload = SelectOptionChangesetPB.fromObject({ cell_identifier: this._cellIdentifier() });
-    if (isSelect) {
-      payload.insert_options.push(option);
-    } else {
-      payload.update_options.push(option);
-    }
-    return DatabaseEventUpdateSelectOption(payload);
-  };
-
   private _cellIdentifier = () => {
     return CellIdPB.fromObject({
       view_id: this.cellIdentifier.viewId,

+ 1 - 2
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_bd_svc.ts

@@ -33,8 +33,7 @@ export class DatabaseBackendService {
   };
 
   createRow = async (rowId?: string) => {
-    const props = { database_id: this.viewId, start_row_id: rowId ?? undefined };
-    const payload = CreateRowPayloadPB.fromObject(props);
+    const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId, start_row_id: rowId ?? undefined });
     return DatabaseEventCreateRow(payload);
   };
 

+ 12 - 10
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_controller.ts

@@ -1,42 +1,44 @@
 import { DatabaseBackendService } from './database_bd_svc';
 import { FieldController, FieldInfo } from './field/field_controller';
 import { DatabaseViewCache } from './view/database_view_cache';
-import { DatabasePB } from '../../../../services/backend/models/flowy-database/grid_entities';
+import { DatabasePB } from '../../../../services/backend';
 import { RowChangedReason, RowInfo } from './row/row_cache';
 import { Err, Ok } from 'ts-results';
 
-export type SubscribeCallback = {
+export type SubscribeCallbacks = {
   onViewChanged?: (data: DatabasePB) => void;
   onRowsChanged?: (rowInfos: readonly RowInfo[], reason: RowChangedReason) => void;
   onFieldsChanged?: (fieldInfos: readonly FieldInfo[]) => void;
 };
 
 export class DatabaseController {
-  private _backendService: DatabaseBackendService;
+  private backendService: DatabaseBackendService;
   fieldController: FieldController;
   databaseViewCache: DatabaseViewCache;
-  private _callback?: SubscribeCallback;
+  private _callback?: SubscribeCallbacks;
 
   constructor(public readonly viewId: string) {
-    this._backendService = new DatabaseBackendService(viewId);
+    this.backendService = new DatabaseBackendService(viewId);
     this.fieldController = new FieldController(viewId);
     this.databaseViewCache = new DatabaseViewCache(viewId, this.fieldController);
   }
 
-  subscribe = (callbacks: SubscribeCallback) => {
+  subscribe = (callbacks: SubscribeCallbacks) => {
     this._callback = callbacks;
-    this.fieldController.subscribeOnFieldsChanged(callbacks.onFieldsChanged);
+    this.fieldController.subscribeOnNumOfFieldsChanged(callbacks.onFieldsChanged);
     this.databaseViewCache.getRowCache().subscribeOnRowsChanged((reason) => {
       this._callback?.onRowsChanged?.(this.databaseViewCache.rowInfos, reason);
     });
   };
 
   open = async () => {
-    const result = await this._backendService.openDatabase();
+    const result = await this.backendService.openDatabase();
     if (result.ok) {
       const database: DatabasePB = result.val;
       this._callback?.onViewChanged?.(database);
       await this.fieldController.loadFields(database.fields);
+      await this.databaseViewCache.listenOnRowsChanged();
+      await this.fieldController.listenOnFieldChanges();
       this.databaseViewCache.initializeWithRows(database.rows);
       return Ok.EMPTY;
     } else {
@@ -45,11 +47,11 @@ export class DatabaseController {
   };
 
   createRow = async () => {
-    return this._backendService.createRow();
+    return this.backendService.createRow();
   };
 
   dispose = async () => {
-    await this._backendService.closeDatabase();
+    await this.backendService.closeDatabase();
     await this.fieldController.dispose();
     await this.databaseViewCache.dispose();
   };

+ 1 - 2
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_bd_svc.ts

@@ -23,7 +23,7 @@ export class FieldBackendService {
 
   updateField = (data: {
     name?: string;
-    fieldType: FieldType;
+    fieldType?: FieldType;
     frozen?: boolean;
     visibility?: boolean;
     width?: number;
@@ -65,7 +65,6 @@ export class FieldBackendService {
 
   deleteField = () => {
     const payload = DeleteFieldPayloadPB.fromObject({ view_id: this.viewId, field_id: this.fieldId });
-
     return DatabaseEventDeleteField(payload);
   };
 

+ 29 - 32
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_controller.ts

@@ -1,51 +1,49 @@
 import { Log } from '../../../../utils/log';
 import { DatabaseBackendService } from '../database_bd_svc';
-import { DatabaseFieldObserver } from './field_observer';
-import { FieldIdPB, FieldPB, IndexFieldPB } from '../../../../../services/backend/models/flowy-database/field_entities';
+import { DatabaseFieldChangesetObserver } from './field_observer';
+import { FieldIdPB, FieldPB, IndexFieldPB } from '../../../../../services/backend';
 import { ChangeNotifier } from '../../../../utils/change_notifier';
 
 export class FieldController {
-  private _fieldListener: DatabaseFieldObserver;
-  private _backendService: DatabaseBackendService;
-  private _fieldNotifier = new FieldNotifier([]);
+  private backendService: DatabaseBackendService;
+  private numOfFieldsObserver: DatabaseFieldChangesetObserver;
+  private numOfFieldsNotifier = new NumOfFieldsNotifier([]);
 
   constructor(public readonly viewId: string) {
-    this._backendService = new DatabaseBackendService(viewId);
-    this._fieldListener = new DatabaseFieldObserver(viewId);
-
-    this._listenOnFieldChanges();
+    this.backendService = new DatabaseBackendService(viewId);
+    this.numOfFieldsObserver = new DatabaseFieldChangesetObserver(viewId);
   }
 
   dispose = async () => {
-    this._fieldNotifier.unsubscribe();
-    await this._fieldListener.unsubscribe();
+    this.numOfFieldsNotifier.unsubscribe();
+    await this.numOfFieldsObserver.unsubscribe();
   };
 
   get fieldInfos(): readonly FieldInfo[] {
-    return this._fieldNotifier.fieldInfos;
+    return this.numOfFieldsNotifier.fieldInfos;
   }
 
   getField = (fieldId: string): FieldInfo | undefined => {
-    return this._fieldNotifier.fieldInfos.find((element) => element.field.id === fieldId);
+    return this.numOfFieldsNotifier.fieldInfos.find((element) => element.field.id === fieldId);
   };
 
   loadFields = async (fieldIds: FieldIdPB[]) => {
-    const result = await this._backendService.getFields(fieldIds);
+    const result = await this.backendService.getFields(fieldIds);
     if (result.ok) {
-      this._fieldNotifier.fieldInfos = result.val.map((field) => new FieldInfo(field));
+      this.numOfFieldsNotifier.fieldInfos = result.val.map((field) => new FieldInfo(field));
     } else {
       Log.error(result.val);
     }
   };
 
-  subscribeOnFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => {
-    return this._fieldNotifier.observer.subscribe((fieldInfos) => {
+  subscribeOnNumOfFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => {
+    return this.numOfFieldsNotifier.observer.subscribe((fieldInfos) => {
       callback?.(fieldInfos);
     });
   };
 
-  _listenOnFieldChanges = () => {
-    this._fieldListener.subscribe({
+  listenOnFieldChanges = async () => {
+    await this.numOfFieldsObserver.subscribe({
       onFieldsChanged: (result) => {
         if (result.ok) {
           const changeset = result.val;
@@ -59,7 +57,7 @@ export class FieldController {
     });
   };
 
-  _deleteFields = (deletedFields: FieldIdPB[]) => {
+  private _deleteFields = (deletedFields: FieldIdPB[]) => {
     if (deletedFields.length === 0) {
       return;
     }
@@ -70,10 +68,10 @@ export class FieldController {
     };
     const newFieldInfos = [...this.fieldInfos];
     newFieldInfos.filter(predicate);
-    this._fieldNotifier.fieldInfos = newFieldInfos;
+    this.numOfFieldsNotifier.fieldInfos = newFieldInfos;
   };
 
-  _insertFields = (insertedFields: IndexFieldPB[]) => {
+  private _insertFields = (insertedFields: IndexFieldPB[]) => {
     if (insertedFields.length === 0) {
       return;
     }
@@ -86,29 +84,28 @@ export class FieldController {
         newFieldInfos.push(fieldInfo);
       }
     });
-    this._fieldNotifier.fieldInfos = newFieldInfos;
+    this.numOfFieldsNotifier.fieldInfos = newFieldInfos;
   };
 
-  _updateFields = (updatedFields: FieldPB[]) => {
+  private _updateFields = (updatedFields: FieldPB[]) => {
     if (updatedFields.length === 0) {
       return;
     }
 
     const newFieldInfos = [...this.fieldInfos];
     updatedFields.forEach((updatedField) => {
-      newFieldInfos.map((element) => {
-        if (element.field.id === updatedField.id) {
-          return updatedField;
-        } else {
-          return element;
-        }
+      const index = newFieldInfos.findIndex((fieldInfo) => {
+        return fieldInfo.field.id === updatedField.id;
       });
+      if (index !== -1) {
+        newFieldInfos.splice(index, 1, new FieldInfo(updatedField));
+      }
     });
-    this._fieldNotifier.fieldInfos = newFieldInfos;
+    this.numOfFieldsNotifier.fieldInfos = newFieldInfos;
   };
 }
 
-class FieldNotifier extends ChangeNotifier<FieldInfo[]> {
+class NumOfFieldsNotifier extends ChangeNotifier<FieldInfo[]> {
   constructor(private _fieldInfos: FieldInfo[]) {
     super();
   }

+ 49 - 13
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_observer.ts

@@ -1,40 +1,76 @@
-import { Err, Ok, Result } from 'ts-results';
-import { DatabaseNotification } from '../../../../../services/backend';
-import { DatabaseFieldChangesetPB } from '../../../../../services/backend/models/flowy-database/field_entities';
-import { FlowyError } from '../../../../../services/backend/models/flowy-error';
+import { Ok, Result } from 'ts-results';
+import { DatabaseNotification, DatabaseFieldChangesetPB, FlowyError, FieldPB } from '../../../../../services/backend';
 import { ChangeNotifier } from '../../../../utils/change_notifier';
 import { DatabaseNotificationObserver } from '../notifications/observer';
 
 type UpdateFieldNotifiedValue = Result<DatabaseFieldChangesetPB, FlowyError>;
 export type DatabaseNotificationCallback = (value: UpdateFieldNotifiedValue) => void;
 
+export class DatabaseFieldChangesetObserver {
+  private notifier?: ChangeNotifier<UpdateFieldNotifiedValue>;
+  private listener?: DatabaseNotificationObserver;
+
+  constructor(public readonly viewId: string) {}
+
+  subscribe = async (callbacks: { onFieldsChanged: DatabaseNotificationCallback }) => {
+    this.notifier = new ChangeNotifier();
+    this.notifier?.observer.subscribe(callbacks.onFieldsChanged);
+
+    this.listener = new DatabaseNotificationObserver({
+      id: this.viewId,
+      parserHandler: (notification, result) => {
+        switch (notification) {
+          case DatabaseNotification.DidUpdateFields:
+            if (result.ok) {
+              this.notifier?.notify(Ok(DatabaseFieldChangesetPB.deserializeBinary(result.val)));
+            } else {
+              this.notifier?.notify(result);
+            }
+            return;
+          default:
+            break;
+        }
+      },
+    });
+    await this.listener.start();
+  };
+
+  unsubscribe = async () => {
+    this.notifier?.unsubscribe();
+    await this.listener?.stop();
+  };
+}
+
+type FieldNotifiedValue = Result<FieldPB, FlowyError>;
+export type FieldNotificationCallback = (value: FieldNotifiedValue) => void;
+
 export class DatabaseFieldObserver {
-  private _notifier?: ChangeNotifier<UpdateFieldNotifiedValue>;
+  private _notifier?: ChangeNotifier<FieldNotifiedValue>;
   private _listener?: DatabaseNotificationObserver;
 
-  constructor(public readonly viewId: string) {}
+  constructor(public readonly fieldId: string) {}
 
-  subscribe = (callbacks: { onFieldsChanged: DatabaseNotificationCallback }) => {
+  subscribe = async (callbacks: { onFieldChanged: FieldNotificationCallback }) => {
     this._notifier = new ChangeNotifier();
-    this._notifier?.observer.subscribe(callbacks.onFieldsChanged);
+    this._notifier?.observer.subscribe(callbacks.onFieldChanged);
 
     this._listener = new DatabaseNotificationObserver({
-      viewId: this.viewId,
+      id: this.fieldId,
       parserHandler: (notification, result) => {
         switch (notification) {
-          case DatabaseNotification.DidUpdateFields:
+          case DatabaseNotification.DidUpdateField:
             if (result.ok) {
-              this._notifier?.notify(Ok(DatabaseFieldChangesetPB.deserializeBinary(result.val)));
+              this._notifier?.notify(Ok(FieldPB.deserializeBinary(result.val)));
             } else {
               this._notifier?.notify(result);
             }
-            return;
+            break;
           default:
             break;
         }
       },
     });
-    return undefined;
+    await this._listener.start();
   };
 
   unsubscribe = async () => {

+ 38 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_bd_svc.ts

@@ -0,0 +1,38 @@
+import {
+  CreateFieldPayloadPB,
+  FieldType,
+  TypeOptionPathPB,
+  UpdateFieldTypePayloadPB,
+} from '../../../../../../services/backend';
+import {
+  DatabaseEventCreateTypeOption,
+  DatabaseEventGetTypeOption,
+  DatabaseEventUpdateFieldType,
+} from '../../../../../../services/backend/events/flowy-database';
+
+export class TypeOptionBackendService {
+  constructor(public readonly viewId: string) {}
+
+  createTypeOption = (fieldType: FieldType) => {
+    const payload = CreateFieldPayloadPB.fromObject({ view_id: this.viewId, field_type: fieldType });
+    return DatabaseEventCreateTypeOption(payload);
+  };
+
+  getTypeOption = (fieldId: string, fieldType: FieldType) => {
+    const payload = TypeOptionPathPB.fromObject({
+      view_id: this.viewId,
+      field_id: fieldId,
+      field_type: fieldType,
+    });
+    return DatabaseEventGetTypeOption(payload);
+  };
+
+  updateTypeOptionType = (fieldId: string, fieldType: FieldType) => {
+    const payload = UpdateFieldTypePayloadPB.fromObject({
+      view_id: this.viewId,
+      field_id: fieldId,
+      field_type: fieldType,
+    });
+    return DatabaseEventUpdateFieldType(payload);
+  };
+}

+ 205 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_context.ts

@@ -0,0 +1,205 @@
+import { None, Ok, Option, Result, Some } from 'ts-results';
+import { TypeOptionController } from './type_option_controller';
+import {
+  CheckboxTypeOptionPB,
+  ChecklistTypeOptionPB,
+  DateTypeOptionPB,
+  FlowyError,
+  MultiSelectTypeOptionPB,
+  NumberTypeOptionPB,
+  SingleSelectTypeOptionPB,
+  URLTypeOptionPB,
+} from '../../../../../../services/backend';
+import { utf8Decoder, utf8Encoder } from '../../cell/data_parser';
+import { DatabaseFieldObserver } from '../field_observer';
+
+abstract class TypeOptionSerde<T> {
+  abstract deserialize(buffer: Uint8Array): T;
+
+  abstract serialize(value: T): Uint8Array;
+}
+
+// RichText
+export function makeRichTextTypeOptionContext(controller: TypeOptionController): RichTextTypeOptionContext {
+  const parser = new RichTextTypeOptionSerde();
+  return new TypeOptionContext<string>(parser, controller);
+}
+
+export type RichTextTypeOptionContext = TypeOptionContext<string>;
+
+class RichTextTypeOptionSerde extends TypeOptionSerde<string> {
+  deserialize(buffer: Uint8Array): string {
+    return utf8Decoder.decode(buffer);
+  }
+
+  serialize(value: string): Uint8Array {
+    return utf8Encoder.encode(value);
+  }
+}
+
+// Number
+export function makeNumberTypeOptionContext(controller: TypeOptionController): NumberTypeOptionContext {
+  const parser = new NumberTypeOptionSerde();
+  return new TypeOptionContext<NumberTypeOptionPB>(parser, controller);
+}
+
+export type NumberTypeOptionContext = TypeOptionContext<NumberTypeOptionPB>;
+
+class NumberTypeOptionSerde extends TypeOptionSerde<NumberTypeOptionPB> {
+  deserialize(buffer: Uint8Array): NumberTypeOptionPB {
+    return NumberTypeOptionPB.deserializeBinary(buffer);
+  }
+
+  serialize(value: NumberTypeOptionPB): Uint8Array {
+    return value.serializeBinary();
+  }
+}
+
+// Checkbox
+export function makeCheckboxTypeOptionContext(controller: TypeOptionController): CheckboxTypeOptionContext {
+  const parser = new CheckboxTypeOptionSerde();
+  return new TypeOptionContext<CheckboxTypeOptionPB>(parser, controller);
+}
+
+export type CheckboxTypeOptionContext = TypeOptionContext<CheckboxTypeOptionPB>;
+
+class CheckboxTypeOptionSerde extends TypeOptionSerde<CheckboxTypeOptionPB> {
+  deserialize(buffer: Uint8Array): CheckboxTypeOptionPB {
+    return CheckboxTypeOptionPB.deserializeBinary(buffer);
+  }
+
+  serialize(value: CheckboxTypeOptionPB): Uint8Array {
+    return value.serializeBinary();
+  }
+}
+
+// URL
+export function makeURLTypeOptionContext(controller: TypeOptionController): URLTypeOptionContext {
+  const parser = new URLTypeOptionSerde();
+  return new TypeOptionContext<URLTypeOptionPB>(parser, controller);
+}
+
+export type URLTypeOptionContext = TypeOptionContext<URLTypeOptionPB>;
+
+class URLTypeOptionSerde extends TypeOptionSerde<URLTypeOptionPB> {
+  deserialize(buffer: Uint8Array): URLTypeOptionPB {
+    return URLTypeOptionPB.deserializeBinary(buffer);
+  }
+
+  serialize(value: URLTypeOptionPB): Uint8Array {
+    return value.serializeBinary();
+  }
+}
+
+// Date
+export function makeDateTypeOptionContext(controller: TypeOptionController): DateTypeOptionContext {
+  const parser = new DateTypeOptionSerde();
+  return new TypeOptionContext<DateTypeOptionPB>(parser, controller);
+}
+
+export type DateTypeOptionContext = TypeOptionContext<DateTypeOptionPB>;
+
+class DateTypeOptionSerde extends TypeOptionSerde<DateTypeOptionPB> {
+  deserialize(buffer: Uint8Array): DateTypeOptionPB {
+    return DateTypeOptionPB.deserializeBinary(buffer);
+  }
+
+  serialize(value: DateTypeOptionPB): Uint8Array {
+    return value.serializeBinary();
+  }
+}
+
+// SingleSelect
+export function makeSingleSelectTypeOptionContext(controller: TypeOptionController): SingleSelectTypeOptionContext {
+  const parser = new SingleSelectTypeOptionSerde();
+  return new TypeOptionContext<SingleSelectTypeOptionPB>(parser, controller);
+}
+
+export type SingleSelectTypeOptionContext = TypeOptionContext<SingleSelectTypeOptionPB>;
+
+class SingleSelectTypeOptionSerde extends TypeOptionSerde<SingleSelectTypeOptionPB> {
+  deserialize(buffer: Uint8Array): SingleSelectTypeOptionPB {
+    return SingleSelectTypeOptionPB.deserializeBinary(buffer);
+  }
+
+  serialize(value: SingleSelectTypeOptionPB): Uint8Array {
+    return value.serializeBinary();
+  }
+}
+
+// Multi-select
+export function makeMultiSelectTypeOptionContext(controller: TypeOptionController): MultiSelectTypeOptionContext {
+  const parser = new MultiSelectTypeOptionSerde();
+  return new TypeOptionContext<MultiSelectTypeOptionPB>(parser, controller);
+}
+
+export type MultiSelectTypeOptionContext = TypeOptionContext<MultiSelectTypeOptionPB>;
+
+class MultiSelectTypeOptionSerde extends TypeOptionSerde<MultiSelectTypeOptionPB> {
+  deserialize(buffer: Uint8Array): MultiSelectTypeOptionPB {
+    return MultiSelectTypeOptionPB.deserializeBinary(buffer);
+  }
+
+  serialize(value: MultiSelectTypeOptionPB): Uint8Array {
+    return value.serializeBinary();
+  }
+}
+
+// Checklist
+export function makeChecklistTypeOptionContext(controller: TypeOptionController): ChecklistTypeOptionContext {
+  const parser = new ChecklistTypeOptionSerde();
+  return new TypeOptionContext<ChecklistTypeOptionPB>(parser, controller);
+}
+
+export type ChecklistTypeOptionContext = TypeOptionContext<ChecklistTypeOptionPB>;
+
+class ChecklistTypeOptionSerde extends TypeOptionSerde<ChecklistTypeOptionPB> {
+  deserialize(buffer: Uint8Array): ChecklistTypeOptionPB {
+    return ChecklistTypeOptionPB.deserializeBinary(buffer);
+  }
+
+  serialize(value: ChecklistTypeOptionPB): Uint8Array {
+    return value.serializeBinary();
+  }
+}
+
+export class TypeOptionContext<T> {
+  private typeOption: Option<T>;
+  private fieldObserver: DatabaseFieldObserver;
+
+  constructor(public readonly parser: TypeOptionSerde<T>, private readonly controller: TypeOptionController) {
+    this.typeOption = None;
+    this.fieldObserver = new DatabaseFieldObserver(controller.fieldId);
+
+    void this.fieldObserver.subscribe({
+      onFieldChanged: () => {
+        void this.getTypeOption();
+      },
+    });
+  }
+
+  get viewId(): string {
+    return this.controller.viewId;
+  }
+
+  getTypeOption = async (): Promise<Result<T, FlowyError>> => {
+    const result = await this.controller.getTypeOption();
+    if (result.ok) {
+      const typeOption = this.parser.deserialize(result.val.type_option_data);
+      this.typeOption = Some(typeOption);
+      return Ok(typeOption);
+    } else {
+      return result;
+    }
+  };
+
+  // Save the typeOption to disk
+  setTypeOption = async (typeOption: T) => {
+    await this.controller.saveTypeOption(this.parser.serialize(typeOption));
+    this.typeOption = Some(typeOption);
+  };
+
+  dispose = async () => {
+    await this.fieldObserver.unsubscribe();
+  };
+}

+ 127 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_controller.ts

@@ -0,0 +1,127 @@
+import { FieldPB, FieldType, TypeOptionPB } from '../../../../../../services/backend';
+import { ChangeNotifier } from '../../../../../utils/change_notifier';
+import { FieldBackendService } from '../field_bd_svc';
+import { Log } from '../../../../../utils/log';
+import { None, Option, Some } from 'ts-results';
+import { FieldInfo } from '../field_controller';
+import { TypeOptionBackendService } from './type_option_bd_svc';
+
+export class TypeOptionController {
+  private fieldNotifier = new ChangeNotifier<FieldPB>();
+  private typeOptionData: Option<TypeOptionPB>;
+  private fieldBackendSvc?: FieldBackendService;
+  private typeOptionBackendSvc: TypeOptionBackendService;
+
+  // Must call [initialize] if the passed-in fieldInfo is None
+  constructor(
+    public readonly viewId: string,
+    private readonly initialFieldInfo: Option<FieldInfo> = None,
+    private readonly defaultFieldType: FieldType = FieldType.RichText
+  ) {
+    this.typeOptionData = None;
+    this.typeOptionBackendSvc = new TypeOptionBackendService(viewId);
+  }
+
+  // It will create a new field for the defaultFieldType if the [initialFieldInfo] is None.
+  // Otherwise, it will get the type option of the [initialFieldInfo]
+  initialize = async () => {
+    if (this.initialFieldInfo.none) {
+      await this.createTypeOption(this.defaultFieldType);
+    } else {
+      await this.getTypeOption();
+    }
+  };
+
+  get fieldId(): string {
+    return this.getFieldInfo().field.id;
+  }
+
+  get fieldType(): FieldType {
+    return this.getFieldInfo().field.field_type;
+  }
+
+  getFieldInfo = (): FieldInfo => {
+    if (this.typeOptionData.none) {
+      if (this.initialFieldInfo.some) {
+        return this.initialFieldInfo.val;
+      } else {
+        throw Error('Unexpect empty type option data. Should call initialize first');
+      }
+    }
+    return new FieldInfo(this.typeOptionData.val.field);
+  };
+
+  switchToField = async (fieldType: FieldType) => {
+    const result = await this.typeOptionBackendSvc.updateTypeOptionType(this.fieldId, fieldType);
+    if (result.ok) {
+      const getResult = await this.typeOptionBackendSvc.getTypeOption(this.fieldId, fieldType);
+      if (getResult.ok) {
+        this.updateTypeOptionData(getResult.val);
+      }
+      return getResult;
+    }
+    return result;
+  };
+
+  setFieldName = async (name: string) => {
+    if (this.typeOptionData.some) {
+      this.typeOptionData.val.field.name = name;
+      void this.fieldBackendSvc?.updateField({ name: name });
+      this.fieldNotifier.notify(this.typeOptionData.val.field);
+    } else {
+      throw Error('Unexpect empty type option data. Should call initialize first');
+    }
+  };
+
+  saveTypeOption = async (data: Uint8Array) => {
+    if (this.typeOptionData.some) {
+      this.typeOptionData.val.type_option_data = data;
+      await this.fieldBackendSvc?.updateTypeOption(data).then((result) => {
+        if (result.err) {
+          Log.error(result.val);
+        }
+      });
+    } else {
+      throw Error('Unexpect empty type option data. Should call initialize first');
+    }
+  };
+
+  deleteField = async () => {
+    if (this.fieldBackendSvc === undefined) {
+      Log.error('Unexpect empty field backend service');
+    }
+    return this.fieldBackendSvc?.deleteField();
+  };
+
+  duplicateField = async () => {
+    if (this.fieldBackendSvc === undefined) {
+      Log.error('Unexpect empty field backend service');
+    }
+    return this.fieldBackendSvc?.duplicateField();
+  };
+
+  // Returns the type option for specific field with specific fieldType
+  getTypeOption = async () => {
+    return this.typeOptionBackendSvc.getTypeOption(this.fieldId, this.fieldType).then((result) => {
+      if (result.ok) {
+        this.updateTypeOptionData(result.val);
+      }
+      return result;
+    });
+  };
+
+  private createTypeOption = (fieldType: FieldType) => {
+    return this.typeOptionBackendSvc.createTypeOption(fieldType).then((result) => {
+      if (result.ok) {
+        this.updateTypeOptionData(result.val);
+      }
+      return result;
+    });
+  };
+
+  private updateTypeOptionData = (typeOptionData: TypeOptionPB) => {
+    this.typeOptionData = Some(typeOptionData);
+    this.fieldBackendSvc = new FieldBackendService(this.viewId, typeOptionData.field.id);
+    this.fieldNotifier.notify(typeOptionData.field);
+  };
+}

+ 3 - 4
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/notifications/observer.ts

@@ -1,16 +1,15 @@
-import { DatabaseNotification } from '../../../../../services/backend';
+import { DatabaseNotification, FlowyError } from '../../../../../services/backend';
 import { AFNotificationObserver } from '../../../../../services/backend/notifications';
 import { DatabaseNotificationParser } from './parser';
-import { FlowyError } from '../../../../../services/backend';
 import { Result } from 'ts-results';
 
 export type ParserHandler = (notification: DatabaseNotification, result: Result<Uint8Array, FlowyError>) => void;
 
 export class DatabaseNotificationObserver extends AFNotificationObserver<DatabaseNotification> {
-  constructor(params: { viewId?: string; parserHandler: ParserHandler }) {
+  constructor(params: { id?: string; parserHandler: ParserHandler }) {
     const parser = new DatabaseNotificationParser({
       callback: params.parserHandler,
-      id: params.viewId,
+      id: params.id,
     });
     super(parser);
   }

+ 32 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/row/row_bd_svc.ts

@@ -0,0 +1,32 @@
+import { CreateRowPayloadPB, RowIdPB } from '../../../../../services/backend';
+import {
+  DatabaseEventCreateRow,
+  DatabaseEventDeleteRow,
+  DatabaseEventDuplicateRow,
+  DatabaseEventGetRow,
+} from '../../../../../services/backend/events/flowy-database';
+
+export class RowBackendService {
+  constructor(public readonly viewId: string) {}
+
+  // Create a row below the row with rowId
+  createRow = (rowId: string) => {
+    const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId, start_row_id: rowId });
+    return DatabaseEventCreateRow(payload);
+  };
+
+  deleteRow = (rowId: string) => {
+    const payload = RowIdPB.fromObject({ view_id: this.viewId, row_id: rowId });
+    return DatabaseEventDeleteRow(payload);
+  };
+
+  duplicateRow = (rowId: string) => {
+    const payload = RowIdPB.fromObject({ view_id: this.viewId, row_id: rowId });
+    return DatabaseEventDuplicateRow(payload);
+  };
+
+  getRow = (rowId: string) => {
+    const payload = RowIdPB.fromObject({ view_id: this.viewId, row_id: rowId });
+    return DatabaseEventGetRow(payload);
+  };
+}

+ 47 - 39
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/row/row_cache.ts

@@ -19,26 +19,26 @@ import { Log } from '../../../../utils/log';
 export type CellByFieldId = Map<string, CellIdentifier>;
 
 export class RowCache {
-  private readonly _rowList: RowList;
-  private readonly _cellCache: CellCache;
-  private readonly _notifier: RowChangeNotifier;
+  private readonly rowList: RowList;
+  private readonly cellCache: CellCache;
+  private readonly notifier: RowChangeNotifier;
 
   constructor(public readonly viewId: string, private readonly getFieldInfos: () => readonly FieldInfo[]) {
-    this._rowList = new RowList();
-    this._cellCache = new CellCache(viewId);
-    this._notifier = new RowChangeNotifier();
+    this.rowList = new RowList();
+    this.cellCache = new CellCache(viewId);
+    this.notifier = new RowChangeNotifier();
   }
 
   get rows(): readonly RowInfo[] {
-    return this._rowList.rows;
+    return this.rowList.rows;
   }
 
   getCellCache = () => {
-    return this._cellCache;
+    return this.cellCache;
   };
 
   loadCells = async (rowId: string): Promise<CellByFieldId> => {
-    const opRow = this._rowList.getRow(rowId);
+    const opRow = this.rowList.getRow(rowId);
     if (opRow.some) {
       return this._toCellMap(opRow.val.row.id, this.getFieldInfos());
     } else {
@@ -54,7 +54,7 @@ export class RowCache {
   };
 
   subscribeOnRowsChanged = (callback: (reason: RowChangedReason, cellMap?: Map<string, CellIdentifier>) => void) => {
-    return this._notifier.observer.subscribe((change) => {
+    return this.notifier.observer.subscribe((change) => {
       if (change.rowId !== undefined) {
         callback(change.reason, this._toCellMap(change.rowId, this.getFieldInfos()));
       } else {
@@ -65,18 +65,19 @@ export class RowCache {
 
   onFieldUpdated = (fieldInfo: FieldInfo) => {
     // Remove the cell data if the corresponding field was changed
-    this._cellCache.removeWithFieldId(fieldInfo.field.id);
+    this.cellCache.removeWithFieldId(fieldInfo.field.id);
   };
 
-  onNumberOfFieldsUpdated = () => {
-    this._notifier.withChange(RowChangedReason.FieldDidChanged);
+  onNumberOfFieldsUpdated = (fieldInfos: readonly FieldInfo[]) => {
+    this.rowList.setFieldInfos(fieldInfos);
+    this.notifier.withChange(RowChangedReason.FieldDidChanged);
   };
 
   initializeRows = (rows: RowPB[]) => {
     rows.forEach((rowPB) => {
-      this._rowList.push(this._toRowInfo(rowPB));
+      this.rowList.push(this._toRowInfo(rowPB));
     });
-    this._notifier.withChange(RowChangedReason.ReorderRows);
+    this.notifier.withChange(RowChangedReason.ReorderRows);
   };
 
   applyRowsChanged = (changeset: RowsChangesetPB) => {
@@ -91,15 +92,15 @@ export class RowCache {
   };
 
   applyReorderRows = (rowIds: string[]) => {
-    this._rowList.reorderByRowIds(rowIds);
-    this._notifier.withChange(RowChangedReason.ReorderRows);
+    this.rowList.reorderByRowIds(rowIds);
+    this.notifier.withChange(RowChangedReason.ReorderRows);
   };
 
   applyReorderSingleRow = (reorderRow: ReorderSingleRowPB) => {
-    const rowInfo = this._rowList.getRow(reorderRow.row_id);
+    const rowInfo = this.rowList.getRow(reorderRow.row_id);
     if (rowInfo !== undefined) {
-      this._rowList.move({ rowId: reorderRow.row_id, fromIndex: reorderRow.old_index, toIndex: reorderRow.new_index });
-      this._notifier.withChange(RowChangedReason.ReorderSingleRow, reorderRow.row_id);
+      this.rowList.move({ rowId: reorderRow.row_id, fromIndex: reorderRow.old_index, toIndex: reorderRow.new_index });
+      this.notifier.withChange(RowChangedReason.ReorderSingleRow, reorderRow.row_id);
     }
   };
 
@@ -108,14 +109,14 @@ export class RowCache {
       return;
     }
     const updatedRow = opRow.row;
-    const option = this._rowList.getRowWithIndex(updatedRow.id);
+    const option = this.rowList.getRowWithIndex(updatedRow.id);
     if (option.some) {
       const { rowInfo, index } = option.val;
-      this._rowList.remove(rowInfo.row.id);
-      this._rowList.insert(index, rowInfo.copyWith({ row: updatedRow }));
+      this.rowList.remove(rowInfo.row.id);
+      this.rowList.insert(index, rowInfo.copyWith({ row: updatedRow }));
     } else {
       const newRowInfo = new RowInfo(this.viewId, this.getFieldInfos(), updatedRow);
-      this._rowList.push(newRowInfo);
+      this.rowList.push(newRowInfo);
     }
   };
 
@@ -126,9 +127,9 @@ export class RowCache {
 
   private _deleteRows = (rowIds: string[]) => {
     rowIds.forEach((rowId) => {
-      const deletedRow = this._rowList.remove(rowId);
+      const deletedRow = this.rowList.remove(rowId);
       if (deletedRow !== undefined) {
-        this._notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
+        this.notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
       }
     });
   };
@@ -136,9 +137,9 @@ export class RowCache {
   private _insertRows = (rows: InsertedRowPB[]) => {
     rows.forEach((insertedRow) => {
       const rowInfo = this._toRowInfo(insertedRow.row);
-      const insertedIndex = this._rowList.insert(insertedRow.index, rowInfo);
+      const insertedIndex = this.rowList.insert(insertedRow.index, rowInfo);
       if (insertedIndex !== undefined) {
-        this._notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
+        this.notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
       }
     });
   };
@@ -152,39 +153,39 @@ export class RowCache {
     updatedRows.forEach((updatedRow) => {
       updatedRow.field_ids.forEach((fieldId) => {
         const key = new CellCacheKey(fieldId, updatedRow.row.id);
-        this._cellCache.remove(key);
+        this.cellCache.remove(key);
       });
 
       rowInfos.push(this._toRowInfo(updatedRow.row));
     });
 
-    const updatedIndexs = this._rowList.insertRows(rowInfos);
+    const updatedIndexs = this.rowList.insertRows(rowInfos);
     updatedIndexs.forEach((row) => {
-      this._notifier.withChange(RowChangedReason.Update, row.rowId);
+      this.notifier.withChange(RowChangedReason.Update, row.rowId);
     });
   };
 
   private _hideRows = (rowIds: string[]) => {
     rowIds.forEach((rowId) => {
-      const deletedRow = this._rowList.remove(rowId);
+      const deletedRow = this.rowList.remove(rowId);
       if (deletedRow !== undefined) {
-        this._notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
+        this.notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
       }
     });
   };
 
   private _displayRows = (insertedRows: InsertedRowPB[]) => {
     insertedRows.forEach((insertedRow) => {
-      const insertedIndex = this._rowList.insert(insertedRow.index, this._toRowInfo(insertedRow.row));
+      const insertedIndex = this.rowList.insert(insertedRow.index, this._toRowInfo(insertedRow.row));
 
       if (insertedIndex !== undefined) {
-        this._notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
+        this.notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
       }
     });
   };
 
   dispose = async () => {
-    this._notifier.dispose();
+    this.notifier.dispose();
   };
 
   private _toRowInfo = (rowPB: RowPB) => {
@@ -219,7 +220,6 @@ class RowList {
       return Some(rowInfo);
     }
   };
-
   getRowWithIndex = (rowId: string): Option<{ rowInfo: RowInfo; index: number }> => {
     const rowInfo = this._rowInfoByRowId.get(rowId);
     if (rowInfo !== undefined) {
@@ -322,6 +322,14 @@ class RowList {
   includes = (rowId: string): boolean => {
     return this._rowInfoByRowId.has(rowId);
   };
+
+  setFieldInfos = (fieldInfos: readonly FieldInfo[]) => {
+    const newRowInfos: RowInfo[] = [];
+    this._rowInfos.forEach((rowInfo) => {
+      newRowInfos.push(rowInfo.copyWith({ fieldInfos: fieldInfos }));
+    });
+    this._rowInfos = newRowInfos;
+  };
 }
 
 export class RowInfo {
@@ -331,8 +339,8 @@ export class RowInfo {
     public readonly row: RowPB
   ) {}
 
-  copyWith = (params: { row?: RowPB }) => {
-    return new RowInfo(this.viewId, this.fieldInfos, params.row || this.row);
+  copyWith = (params: { row?: RowPB; fieldInfos?: readonly FieldInfo[] }) => {
+    return new RowInfo(this.viewId, params.fieldInfos || this.fieldInfos, params.row || this.row);
   };
 }
 

+ 22 - 22
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/view/database_view_cache.ts

@@ -1,63 +1,63 @@
 import { DatabaseViewRowsObserver } from './view_row_observer';
-import { RowCache, RowChangedReason, RowInfo } from '../row/row_cache';
+import { RowCache, RowInfo } from '../row/row_cache';
 import { FieldController } from '../field/field_controller';
-import { RowPB } from '../../../../../services/backend/models/flowy-database/row_entities';
+import { RowPB } from '../../../../../services/backend';
 import { Subscription } from 'rxjs';
 
 export class DatabaseViewCache {
-  private readonly _rowsObserver: DatabaseViewRowsObserver;
-  private readonly _rowCache: RowCache;
-  private readonly _fieldSubscription?: Subscription;
+  private readonly rowsObserver: DatabaseViewRowsObserver;
+  private readonly rowCache: RowCache;
+  private readonly fieldSubscription?: Subscription;
 
   constructor(public readonly viewId: string, fieldController: FieldController) {
-    this._rowsObserver = new DatabaseViewRowsObserver(viewId);
-    this._rowCache = new RowCache(viewId, () => fieldController.fieldInfos);
-    this._fieldSubscription = fieldController.subscribeOnFieldsChanged((fieldInfos) => {
+    this.rowsObserver = new DatabaseViewRowsObserver(viewId);
+    this.rowCache = new RowCache(viewId, () => fieldController.fieldInfos);
+    this.fieldSubscription = fieldController.subscribeOnNumOfFieldsChanged((fieldInfos) => {
       fieldInfos.forEach((fieldInfo) => {
-        this._rowCache.onFieldUpdated(fieldInfo);
+        this.rowCache.onFieldUpdated(fieldInfo);
       });
+      this.rowCache.onNumberOfFieldsUpdated(fieldInfos);
     });
-    this._listenOnRowsChanged();
   }
 
   initializeWithRows = (rows: RowPB[]) => {
-    this._rowCache.initializeRows(rows);
+    this.rowCache.initializeRows(rows);
   };
 
   get rowInfos(): readonly RowInfo[] {
-    return this._rowCache.rows;
+    return this.rowCache.rows;
   }
 
   getRowCache = () => {
-    return this._rowCache;
+    return this.rowCache;
   };
 
   dispose = async () => {
-    this._fieldSubscription?.unsubscribe();
-    await this._rowsObserver.unsubscribe();
-    await this._rowCache.dispose();
+    this.fieldSubscription?.unsubscribe();
+    await this.rowsObserver.unsubscribe();
+    await this.rowCache.dispose();
   };
 
-  _listenOnRowsChanged = () => {
-    this._rowsObserver.subscribe({
+  listenOnRowsChanged = async () => {
+    await this.rowsObserver.subscribe({
       onRowsVisibilityChanged: (result) => {
         if (result.ok) {
-          this._rowCache.applyRowsVisibility(result.val);
+          this.rowCache.applyRowsVisibility(result.val);
         }
       },
       onNumberOfRowsChanged: (result) => {
         if (result.ok) {
-          this._rowCache.applyRowsChanged(result.val);
+          this.rowCache.applyRowsChanged(result.val);
         }
       },
       onReorderRows: (result) => {
         if (result.ok) {
-          this._rowCache.applyReorderRows(result.val);
+          this.rowCache.applyReorderRows(result.val);
         }
       },
       onReorderSingleRow: (result) => {
         if (result.ok) {
-          this._rowCache.applyReorderSingleRow(result.val);
+          this.rowCache.applyReorderSingleRow(result.val);
         }
       },
     });

+ 23 - 22
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/view/view_row_observer.ts

@@ -16,57 +16,57 @@ export type ReorderRowsNotifyValue = Result<string[], FlowyError>;
 export type ReorderSingleRowNotifyValue = Result<ReorderSingleRowPB, FlowyError>;
 
 export class DatabaseViewRowsObserver {
-  private _rowsVisibilityNotifier = new ChangeNotifier<RowsVisibilityNotifyValue>();
-  private _rowsNotifier = new ChangeNotifier<RowsNotifyValue>();
-  private _reorderRowsNotifier = new ChangeNotifier<ReorderRowsNotifyValue>();
-  private _reorderSingleRowNotifier = new ChangeNotifier<ReorderSingleRowNotifyValue>();
+  private rowsVisibilityNotifier = new ChangeNotifier<RowsVisibilityNotifyValue>();
+  private rowsNotifier = new ChangeNotifier<RowsNotifyValue>();
+  private reorderRowsNotifier = new ChangeNotifier<ReorderRowsNotifyValue>();
+  private reorderSingleRowNotifier = new ChangeNotifier<ReorderSingleRowNotifyValue>();
 
   private _listener?: DatabaseNotificationObserver;
 
   constructor(public readonly viewId: string) {}
 
-  subscribe = (callbacks: {
+  subscribe = async (callbacks: {
     onRowsVisibilityChanged?: (value: RowsVisibilityNotifyValue) => void;
     onNumberOfRowsChanged?: (value: RowsNotifyValue) => void;
     onReorderRows?: (value: ReorderRowsNotifyValue) => void;
     onReorderSingleRow?: (value: ReorderSingleRowNotifyValue) => void;
   }) => {
     //
-    this._rowsVisibilityNotifier.observer.subscribe(callbacks.onRowsVisibilityChanged);
-    this._rowsNotifier.observer.subscribe(callbacks.onNumberOfRowsChanged);
-    this._reorderRowsNotifier.observer.subscribe(callbacks.onReorderRows);
-    this._reorderSingleRowNotifier.observer.subscribe(callbacks.onReorderSingleRow);
+    this.rowsVisibilityNotifier.observer.subscribe(callbacks.onRowsVisibilityChanged);
+    this.rowsNotifier.observer.subscribe(callbacks.onNumberOfRowsChanged);
+    this.reorderRowsNotifier.observer.subscribe(callbacks.onReorderRows);
+    this.reorderSingleRowNotifier.observer.subscribe(callbacks.onReorderSingleRow);
 
     this._listener = new DatabaseNotificationObserver({
-      viewId: this.viewId,
+      id: this.viewId,
       parserHandler: (notification, result) => {
         switch (notification) {
           case DatabaseNotification.DidUpdateViewRowsVisibility:
             if (result.ok) {
-              this._rowsVisibilityNotifier.notify(Ok(RowsVisibilityChangesetPB.deserializeBinary(result.val)));
+              this.rowsVisibilityNotifier.notify(Ok(RowsVisibilityChangesetPB.deserializeBinary(result.val)));
             } else {
-              this._rowsVisibilityNotifier.notify(result);
+              this.rowsVisibilityNotifier.notify(result);
             }
             break;
           case DatabaseNotification.DidUpdateViewRows:
             if (result.ok) {
-              this._rowsNotifier.notify(Ok(RowsChangesetPB.deserializeBinary(result.val)));
+              this.rowsNotifier.notify(Ok(RowsChangesetPB.deserializeBinary(result.val)));
             } else {
-              this._rowsNotifier.notify(result);
+              this.rowsNotifier.notify(result);
             }
             break;
           case DatabaseNotification.DidReorderRows:
             if (result.ok) {
-              this._reorderRowsNotifier.notify(Ok(ReorderAllRowsPB.deserializeBinary(result.val).row_orders));
+              this.reorderRowsNotifier.notify(Ok(ReorderAllRowsPB.deserializeBinary(result.val).row_orders));
             } else {
-              this._reorderRowsNotifier.notify(result);
+              this.reorderRowsNotifier.notify(result);
             }
             break;
           case DatabaseNotification.DidReorderSingleRow:
             if (result.ok) {
-              this._reorderSingleRowNotifier.notify(Ok(ReorderSingleRowPB.deserializeBinary(result.val)));
+              this.reorderSingleRowNotifier.notify(Ok(ReorderSingleRowPB.deserializeBinary(result.val)));
             } else {
-              this._reorderSingleRowNotifier.notify(result);
+              this.reorderSingleRowNotifier.notify(result);
             }
             break;
           default:
@@ -74,13 +74,14 @@ export class DatabaseViewRowsObserver {
         }
       },
     });
+    await this._listener.start();
   };
 
   unsubscribe = async () => {
-    this._rowsVisibilityNotifier.unsubscribe();
-    this._reorderRowsNotifier.unsubscribe();
-    this._rowsNotifier.unsubscribe();
-    this._reorderSingleRowNotifier.unsubscribe();
+    this.rowsVisibilityNotifier.unsubscribe();
+    this.reorderRowsNotifier.unsubscribe();
+    this.rowsNotifier.unsubscribe();
+    this.reorderSingleRowNotifier.unsubscribe();
     await this._listener?.stop();
   };
 }

+ 3 - 4
frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/app/app_observer.ts

@@ -6,15 +6,14 @@ import { FolderNotificationObserver } from '../notifications/observer';
 export type AppUpdateNotifyValue = Result<AppPB, FlowyError>;
 export type AppUpdateNotifyCallback = (value: AppUpdateNotifyValue) => void;
 
-export class WorkspaceObserver {
+export class AppObserver {
   _appNotifier = new ChangeNotifier<AppUpdateNotifyValue>();
   _listener?: FolderNotificationObserver;
 
   constructor(public readonly appId: string) {}
 
-  subscribe = (callbacks: { onAppChanged: AppUpdateNotifyCallback }) => {
+  subscribe = async (callbacks: { onAppChanged: AppUpdateNotifyCallback }) => {
     this._appNotifier?.observer.subscribe(callbacks.onAppChanged);
-
     this._listener = new FolderNotificationObserver({
       viewId: this.appId,
       parserHandler: (notification, result) => {
@@ -31,7 +30,7 @@ export class WorkspaceObserver {
         }
       },
     });
-    return undefined;
+    await this._listener.start();
   };
 
   unsubscribe = async () => {

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/view/view_observer.ts

@@ -18,7 +18,7 @@ export class ViewObserver {
 
   constructor(public readonly viewId: string) {}
 
-  subscribe = (callbacks: {
+  subscribe = async (callbacks: {
     onViewUpdate?: (value: UpdateViewNotifyValue) => void;
     onViewDelete?: (value: DeleteViewNotifyValue) => void;
     onViewRestored?: (value: RestoreViewNotifyValue) => void;
@@ -77,7 +77,7 @@ export class ViewObserver {
         }
       },
     });
-    return undefined;
+    await this._listener.start();
   };
 
   unsubscribe = async () => {

+ 18 - 15
frontend/appflowy_tauri/src/appflowy_app/stores/effects/folder/workspace/workspace_observer.ts

@@ -9,32 +9,35 @@ export type WorkspaceNotifyValue = Result<WorkspacePB, FlowyError>;
 export type WorkspaceNotifyCallback = (value: WorkspaceNotifyValue) => void;
 
 export class WorkspaceObserver {
-  private _appListNotifier = new ChangeNotifier<AppListNotifyValue>();
-  private _workspaceNotifier = new ChangeNotifier<WorkspaceNotifyValue>();
-  private _listener?: FolderNotificationObserver;
+  private appListNotifier = new ChangeNotifier<AppListNotifyValue>();
+  private workspaceNotifier = new ChangeNotifier<WorkspaceNotifyValue>();
+  private listener?: FolderNotificationObserver;
 
   constructor(public readonly workspaceId: string) {}
 
-  subscribe = (callbacks: { onAppListChanged: AppListNotifyCallback; onWorkspaceChanged: WorkspaceNotifyCallback }) => {
-    this._appListNotifier?.observer.subscribe(callbacks.onAppListChanged);
-    this._workspaceNotifier?.observer.subscribe(callbacks.onWorkspaceChanged);
+  subscribe = async (callbacks: {
+    onAppListChanged: AppListNotifyCallback;
+    onWorkspaceChanged: WorkspaceNotifyCallback;
+  }) => {
+    this.appListNotifier?.observer.subscribe(callbacks.onAppListChanged);
+    this.workspaceNotifier?.observer.subscribe(callbacks.onWorkspaceChanged);
 
-    this._listener = new FolderNotificationObserver({
+    this.listener = new FolderNotificationObserver({
       viewId: this.workspaceId,
       parserHandler: (notification, result) => {
         switch (notification) {
           case FolderNotification.DidUpdateWorkspace:
             if (result.ok) {
-              this._workspaceNotifier?.notify(Ok(WorkspacePB.deserializeBinary(result.val)));
+              this.workspaceNotifier?.notify(Ok(WorkspacePB.deserializeBinary(result.val)));
             } else {
-              this._workspaceNotifier?.notify(result);
+              this.workspaceNotifier?.notify(result);
             }
             break;
           case FolderNotification.DidUpdateWorkspaceApps:
             if (result.ok) {
-              this._appListNotifier?.notify(Ok(RepeatedAppPB.deserializeBinary(result.val).items));
+              this.appListNotifier?.notify(Ok(RepeatedAppPB.deserializeBinary(result.val).items));
             } else {
-              this._appListNotifier?.notify(result);
+              this.appListNotifier?.notify(result);
             }
             break;
           default:
@@ -42,12 +45,12 @@ export class WorkspaceObserver {
         }
       },
     });
-    return undefined;
+    await this.listener.start();
   };
 
   unsubscribe = async () => {
-    this._appListNotifier.unsubscribe();
-    this._workspaceNotifier.unsubscribe();
-    await this._listener?.stop();
+    this.appListNotifier.unsubscribe();
+    this.workspaceNotifier.unsubscribe();
+    await this.listener?.stop();
   };
 }

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/stores/reducers/current-user/slice.ts

@@ -8,7 +8,7 @@ export interface ICurrentUser {
   email?: string;
   token?: string;
   isAuthenticated: boolean;
-  workspaceSetting?: WorkspaceSettingPB,
+  workspaceSetting?: WorkspaceSettingPB;
 }
 
 const initialState: ICurrentUser | null = {
@@ -16,7 +16,7 @@ const initialState: ICurrentUser | null = {
   displayName: 'Me 😃',
   email: `${nanoid(4)}@gmail.com`,
   token: nanoid(8),
-  isAuthenticated: true,
+  isAuthenticated: false,
 };
 
 export const currentUserSlice = createSlice({

+ 12 - 6
frontend/appflowy_tauri/src/services/backend/notifications/observer.ts

@@ -1,6 +1,6 @@
-import { listen, UnlistenFn } from '@tauri-apps/api/event';
-import { SubscribeObject } from '../models/flowy-notification';
-import { NotificationParser } from './parser';
+import { listen, UnlistenFn } from "@tauri-apps/api/event";
+import { SubscribeObject } from "../models/flowy-notification";
+import { NotificationParser } from "./parser";
 
 export abstract class AFNotificationObserver<T> {
   parser?: NotificationParser<T> | null;
@@ -11,9 +11,15 @@ export abstract class AFNotificationObserver<T> {
   }
 
   async start() {
-    this._listener = await listen('af-notification', (notification) => {
-      const object = SubscribeObject.fromObject(notification.payload as {});
-      this.parser?.parse(object);
+    this._listener = await listen("af-notification", (notification) => {
+      const object: SubscribeObject = SubscribeObject.fromObject(notification.payload as {});
+      if (this.parser?.id !== undefined) {
+        if (object.id === this.parser.id) {
+          this.parser?.parse(object);
+        }
+      } else {
+        this.parser?.parse(object);
+      }
     });
   }
 

+ 0 - 24
frontend/rust-lib/.cargo/config.toml

@@ -1,24 +0,0 @@
-#[build]
-#target-dir = "./bin"
-
-[target.x86_64-apple-darwin]
-rustflags=["-C", "link-arg=-mmacosx-version-min=10.11"]
-
-[target.aarch64-apple-darwin]
-rustflags=["-C", "link-arg=-mmacosx-version-min=10.11"]
-
-[target.aarch64-linux-android]
-ar = "path-to-ndk/llvm-ar"
-linker = "path-to-ndk/aarch64-linux-android29-clang"
-
-[target.armv7-linux-androideabi]
-ar = "path-to-ndk/llvm-ar"
-linker = "path-to-ndk/armv7a-linux-androideabi29-clang"
-
-[target.i686-linux-android]
-ar = "path-to-ndk/llvm-ar"
-linker = "path-to-ndk/i686-linux-android29-clang"
-
-[target.x86_64-linux-android]
-ar = "path-to-ndk/llvm-ar"
-linker = "path-to-ndk/x86_64-linux-android29-clang"

+ 444 - 85
frontend/rust-lib/Cargo.lock

@@ -85,19 +85,20 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
 
 [[package]]
 name = "async-stream"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
+checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e"
 dependencies = [
  "async-stream-impl",
  "futures-core",
+ "pin-project-lite",
 ]
 
 [[package]]
 name = "async-stream-impl"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
+checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -106,9 +107,9 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.61"
+version = "0.1.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282"
+checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -143,9 +144,55 @@ dependencies = [
 
 [[package]]
 name = "autocfg"
-version = "1.0.1"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "axum"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "itoa 1.0.5",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "sync_wrapper",
+ "tower",
+ "tower-http",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "mime",
+ "rustversion",
+ "tower-layer",
+ "tower-service",
+]
 
 [[package]]
 name = "backtrace"
@@ -168,6 +215,21 @@ version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
+[[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
+[[package]]
+name = "basic-toml"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e819b667739967cd44d308b8c7b71305d8bb0729ac44a248aa08f33d01950b4"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "bincode"
 version = "1.3.3"
@@ -218,19 +280,19 @@ dependencies = [
 
 [[package]]
 name = "borsh"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa"
+checksum = "40f9ca3698b2e4cb7c15571db0abc5551dca417a21ae8140460b50309bb2cc62"
 dependencies = [
  "borsh-derive",
- "hashbrown 0.11.2",
+ "hashbrown",
 ]
 
 [[package]]
 name = "borsh-derive"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775"
+checksum = "598b3eacc6db9c3ee57b22707ad8f6a8d2f6d442bfe24ffeb8cbb70ca59e6a35"
 dependencies = [
  "borsh-derive-internal",
  "borsh-schema-derive-internal",
@@ -241,9 +303,9 @@ dependencies = [
 
 [[package]]
 name = "borsh-derive-internal"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065"
+checksum = "186b734fa1c9f6743e90c95d7233c9faab6360d1a96d4ffa19d9cfd1e9350f8a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -252,9 +314,9 @@ dependencies = [
 
 [[package]]
 name = "borsh-schema-derive-internal"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0"
+checksum = "99b7ff1008316626f485991b960ade129253d4034014616b94f309a15366cc49"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -318,9 +380,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "bytes"
-version = "1.3.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
 dependencies = [
  "serde",
 ]
@@ -380,15 +442,6 @@ dependencies = [
  "phf_codegen",
 ]
 
-[[package]]
-name = "claim"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f81099d6bb72e1df6d50bb2347224b666a670912bb7f06dbe867a4a070ab3ce8"
-dependencies = [
- "autocfg",
-]
-
 [[package]]
 name = "clap"
 version = "2.34.0"
@@ -455,7 +508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
 dependencies = [
  "lazy_static",
- "nom",
+ "nom 5.1.2",
  "serde",
  "yaml-rust",
 ]
@@ -475,6 +528,42 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "console-api"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e57ff02e8ad8e06ab9731d5dc72dc23bef9200778eae1a89d555d8c42e5d4a86"
+dependencies = [
+ "prost",
+ "prost-types",
+ "tonic",
+ "tracing-core",
+]
+
+[[package]]
+name = "console-subscriber"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22a3a81dfaf6b66bce5d159eddae701e3a002f194d378cbf7be5f053c281d9be"
+dependencies = [
+ "console-api",
+ "crossbeam-channel",
+ "crossbeam-utils",
+ "futures",
+ "hdrhistogram",
+ "humantime",
+ "prost-types",
+ "serde",
+ "serde_json",
+ "thread_local",
+ "tokio",
+ "tokio-stream",
+ "tonic",
+ "tracing",
+ "tracing-core",
+ "tracing-subscriber 0.3.16",
+]
+
 [[package]]
 name = "convert_case"
 version = "0.4.0"
@@ -506,6 +595,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
 [[package]]
 name = "criterion"
 version = "0.3.6"
@@ -578,9 +676,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.14"
+version = "0.8.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
 dependencies = [
  "cfg-if",
 ]
@@ -908,6 +1006,16 @@ dependencies = [
  "instant",
 ]
 
+[[package]]
+name = "flate2"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
 [[package]]
 name = "flowy-ast"
 version = "0.1.0"
@@ -996,6 +1104,7 @@ name = "flowy-core"
 version = "0.1.0"
 dependencies = [
  "bytes",
+ "console-subscriber",
  "database-model",
  "flowy-client-ws",
  "flowy-database",
@@ -1129,7 +1238,7 @@ dependencies = [
  "strum_macros",
  "tokio",
  "tracing",
- "tracing-subscriber",
+ "tracing-subscriber 0.2.25",
  "unicode-segmentation",
  "url",
  "ws-model",
@@ -1365,7 +1474,6 @@ name = "flowy-test"
 version = "0.1.0"
 dependencies = [
  "bytes",
- "claim",
  "fake",
  "flowy-client-sync",
  "flowy-core",
@@ -1462,9 +1570,9 @@ dependencies = [
 
 [[package]]
 name = "futures"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
+checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1477,9 +1585,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1487,15 +1595,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1504,15 +1612,15 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
+checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1521,21 +1629,21 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
 
 [[package]]
 name = "futures-task"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
 
 [[package]]
 name = "futures-util"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1654,20 +1762,24 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
 
 [[package]]
 name = "hashbrown"
-version = "0.11.2"
+version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 dependencies = [
  "ahash",
 ]
 
 [[package]]
-name = "hashbrown"
-version = "0.12.3"
+name = "hdrhistogram"
+version = "7.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8"
 dependencies = [
- "ahash",
+ "base64 0.13.1",
+ "byteorder",
+ "flate2",
+ "nom 7.1.3",
+ "num-traits",
 ]
 
 [[package]]
@@ -1729,6 +1841,12 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "http-range-header"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
+
 [[package]]
 name = "httparse"
 version = "1.8.0"
@@ -1747,6 +1865,12 @@ version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
 
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
 [[package]]
 name = "hyper"
 version = "0.14.23"
@@ -1771,6 +1895,18 @@ dependencies = [
  "want",
 ]
 
+[[package]]
+name = "hyper-timeout"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
+dependencies = [
+ "hyper",
+ "pin-project-lite",
+ "tokio",
+ "tokio-io-timeout",
+]
+
 [[package]]
 name = "hyper-tls"
 version = "0.5.0"
@@ -1859,7 +1995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
 dependencies = [
  "autocfg",
- "hashbrown 0.12.3",
+ "hashbrown",
  "serde",
 ]
 
@@ -1985,7 +2121,7 @@ dependencies = [
  "tracing-bunyan-formatter",
  "tracing-core",
  "tracing-log",
- "tracing-subscriber",
+ "tracing-subscriber 0.2.25",
 ]
 
 [[package]]
@@ -2089,12 +2225,27 @@ dependencies = [
  "regex-automata",
 ]
 
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata",
+]
+
 [[package]]
 name = "matches"
 version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
 
+[[package]]
+name = "matchit"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
+
 [[package]]
 name = "md5"
 version = "0.7.0"
@@ -2143,6 +2294,12 @@ version = "0.3.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
 
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
 [[package]]
 name = "miniz_oxide"
 version = "0.6.2"
@@ -2161,7 +2318,7 @@ dependencies = [
  "libc",
  "log",
  "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -2202,6 +2359,16 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
 [[package]]
 name = "num-integer"
 version = "0.1.45"
@@ -2242,9 +2409,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.17.0"
+version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
 
 [[package]]
 name = "oorandom"
@@ -2374,7 +2541,7 @@ dependencies = [
  "libc",
  "redox_syscall 0.2.16",
  "smallvec",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -2639,6 +2806,38 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "prost"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88"
+dependencies = [
+ "prost",
+]
+
 [[package]]
 name = "protobuf"
 version = "2.28.0"
@@ -2952,11 +3151,11 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.11.13"
+version = "0.11.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
+checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
 dependencies = [
- "base64",
+ "base64 0.21.0",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -3004,7 +3203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15"
 dependencies = [
  "bytecheck",
- "hashbrown 0.12.3",
+ "hashbrown",
  "ptr_meta",
  "rend",
  "rkyv_derive",
@@ -3024,9 +3223,9 @@ dependencies = [
 
 [[package]]
 name = "rust_decimal"
-version = "1.27.0"
+version = "1.28.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33c321ee4e17d2b7abe12b5d20c1231db708dd36185c8a21e9de5fed6da4dbe9"
+checksum = "e13cf35f7140155d02ba4ec3294373d513a3c7baa8364c162b030e33c61520a8"
 dependencies = [
  "arrayvec 0.7.2",
  "borsh",
@@ -3065,6 +3264,12 @@ dependencies = [
  "semver",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
+
 [[package]]
 name = "rusty-money"
 version = "0.4.1"
@@ -3096,7 +3301,7 @@ version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
 dependencies = [
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -3366,15 +3571,21 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.107"
+version = "1.0.109"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
 dependencies = [
  "proc-macro2",
  "quote",
  "unicode-ident",
 ]
 
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
 [[package]]
 name = "tempfile"
 version = "3.3.0"
@@ -3517,22 +3728,33 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.19.2"
+version = "1.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
+checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
 dependencies = [
+ "autocfg",
  "bytes",
  "libc",
  "memchr",
  "mio",
  "num_cpus",
- "once_cell",
  "parking_lot 0.12.1",
  "pin-project-lite",
  "signal-hook-registry",
  "socket2",
  "tokio-macros",
- "winapi",
+ "tracing",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "tokio-io-timeout"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
+dependencies = [
+ "pin-project-lite",
+ "tokio",
 ]
 
 [[package]]
@@ -3556,6 +3778,17 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "tokio-stream"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
 [[package]]
 name = "tokio-tungstenite"
 version = "0.15.0"
@@ -3585,13 +3818,90 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.5.10"
+version = "0.5.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
 dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "tonic"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "axum",
+ "base64 0.13.1",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-timeout",
+ "percent-encoding",
+ "pin-project",
+ "prost",
+ "prost-derive",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+ "tracing-futures",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "indexmap",
+ "pin-project",
+ "pin-project-lite",
+ "rand 0.8.5",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-range-header",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
 [[package]]
 name = "tower-service"
 version = "0.3.2"
@@ -3619,7 +3929,7 @@ checksum = "9965507e507f12c8901432a33e31131222abac31edd90cabbcf85cf544b7127a"
 dependencies = [
  "chrono",
  "crossbeam-channel",
- "tracing-subscriber",
+ "tracing-subscriber 0.2.25",
 ]
 
 [[package]]
@@ -3647,7 +3957,7 @@ dependencies = [
  "tracing",
  "tracing-core",
  "tracing-log",
- "tracing-subscriber",
+ "tracing-subscriber 0.2.25",
 ]
 
 [[package]]
@@ -3660,6 +3970,16 @@ dependencies = [
  "valuable",
 ]
 
+[[package]]
+name = "tracing-futures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
+dependencies = [
+ "pin-project",
+ "tracing",
+]
+
 [[package]]
 name = "tracing-log"
 version = "0.1.3"
@@ -3690,7 +4010,7 @@ dependencies = [
  "ansi_term",
  "chrono",
  "lazy_static",
- "matchers",
+ "matchers 0.0.1",
  "regex",
  "serde",
  "serde_json",
@@ -3703,6 +4023,21 @@ dependencies = [
  "tracing-serde",
 ]
 
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
+dependencies = [
+ "matchers 0.1.0",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+]
+
 [[package]]
 name = "try-lock"
 version = "0.2.4"
@@ -3711,17 +4046,17 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
 
 [[package]]
 name = "trybuild"
-version = "1.0.75"
+version = "1.0.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1212c215a87a183687a7cc7065901b1a98da6b37277d51a1b5faedbb4efd4f3"
+checksum = "a44da5a6f2164c8e14d3bbc0657d69c5966af9f5f6930d4f600b1f5c4a673413"
 dependencies = [
+ "basic-toml",
  "glob",
  "once_cell",
  "serde",
  "serde_derive",
  "serde_json",
  "termcolor",
- "toml",
 ]
 
 [[package]]
@@ -3730,7 +4065,7 @@ version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
 dependencies = [
- "base64",
+ "base64 0.13.1",
  "byteorder",
  "bytes",
  "http",
@@ -4083,6 +4418,30 @@ dependencies = [
  "windows_x86_64_msvc",
 ]
 
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
 [[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.42.1"

+ 2 - 0
frontend/rust-lib/dart-ffi/.cargo/config.toml

@@ -0,0 +1,2 @@
+[build]
+rustflags = ["--cfg", "tokio_unstable"]

+ 6 - 6
frontend/rust-lib/dart-ffi/Cargo.toml

@@ -13,14 +13,14 @@ crate-type = ["staticlib"]
 
 [dependencies]
 allo-isolate = { version = "^0.1", features = ["catch-unwind"] }
-byteorder = { version = "1.3.4" }
-protobuf = { version = "2.20.0" }
-tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
-log = "0.4.14"
+byteorder = { version = "1.4.3" }
+protobuf = { version = "2.28.0" }
+tokio = { version = "1.26", features = ["full", "rt-multi-thread", "tracing"] }
+log = "0.4.17"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = { version = "1.0" }
-bytes = { version = "1.0" }
-crossbeam-utils = "0.8.7"
+bytes = { version = "1.4" }
+crossbeam-utils = "0.8.15"
 lazy_static = "1.4.0"
 parking_lot = "0.12.1"
 tracing = { version = "0.1", features = ["log"] }

+ 2 - 2
frontend/rust-lib/flowy-ast/Cargo.toml

@@ -6,6 +6,6 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-syn = { version = "1.0.60", features = ["extra-traits", "parsing", "derive", "full"]}
+syn = { version = "1.0.109", features = ["extra-traits", "parsing", "derive", "full"]}
 quote = "1.0"
-proc-macro2 = "1.0"
+proc-macro2 = "1.0"

+ 4 - 4
frontend/rust-lib/flowy-client-sync/Cargo.toml

@@ -14,14 +14,14 @@ database-model = { path = "../../../shared-lib/database-model" }
 revision-model = { path = "../../../shared-lib/revision-model" }
 document-model = { path = "../../../shared-lib/document-model" }
 flowy-sync = { path = "../../../shared-lib/flowy-sync" }
-bytes = "1.0"
-tokio = { version = "1", features = ["full"] }
+bytes = "1.4"
+tokio = { version = "1.26", features = ["full"] }
 serde = { version = "1.0", features = ["derive", "rc"] }
 serde_json = {version = "1.0"}
 dissimilar = "1.0"
 tracing = { version = "0.1", features = ["log"] }
-url = "2.2"
+url = "2.3"
 strum = "0.21"
 strum_macros = "0.21"
-chrono = "0.4.19"
+chrono = "0.4.23"
 parking_lot = "0.12.1"

+ 7 - 7
frontend/rust-lib/flowy-codegen/Cargo.toml

@@ -6,7 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-log = "0.4.14"
+log = "0.4.17"
 serde = { version = "1.0", features = ["derive"]}
 serde_json = "1.0"
 flowy-ast = { path = "../flowy-ast"}
@@ -15,16 +15,16 @@ quote = "1.0"
 cmd_lib = { version = "1.3.0", optional = true }
 protoc-rust = { version = "2", optional = true }
 walkdir = { version = "2", optional = true }
-similar = { version = "1.2.2", optional = true }
-syn = { version = "1.0.60", features = ["extra-traits", "parsing", "derive", "full"] }
+similar = { version = "1.3.0", optional = true }
+syn = { version = "1.0.109", features = ["extra-traits", "parsing", "derive", "full"] }
 fancy-regex = { version = "0.10.0", optional = true }
 lazy_static = { version = "1.4.0", optional = true }
-tera = { version = "1.5.0", optional = true}
+tera = { version = "1.17.1", optional = true}
 itertools = { version = "0.10", optional = true }
 phf = { version = "0.8.0", features = ["macros"], optional = true }
-console = {version = "0.14.0", optional = true}
+console = {version = "0.14.1", optional = true}
 protoc-bin-vendored = { version = "3.0", optional = true }
-toml = {version = "0.5.8", optional = true}
+toml = {version = "0.5.11", optional = true}
 
 
 
@@ -47,4 +47,4 @@ proto_gen = [
 dart_event = ["walkdir", "tera", ]
 dart = ["proto_gen", "dart_event"]
 ts_event = ["walkdir", "tera", ]
-ts = ["proto_gen", "ts_event"]
+ts = ["proto_gen", "ts_event"]

+ 3 - 2
frontend/rust-lib/flowy-codegen/src/ts_event/event_template.tera

@@ -24,14 +24,15 @@ export async function {{ event_func_name }}(): Promise<Result<{{ output_deserial
     if (result.code == 0) {
     {%- if has_output  %}
         let object = {{ output_deserializer }}.deserializeBinary(result.payload);
-        console.log("Success:" + JSON.stringify(object.toObject()))
+        console.log({{ event_func_name }}.name, object);
         return Ok(object);
     {%- else %}
+        console.log({{ event_func_name }}.name);
         return Ok.EMPTY;
     {%- endif %}
     } else {
         let error = {{ error_deserializer }}.deserializeBinary(result.payload);
-        console.log("Error:" + JSON.stringify(error.toObject()))
+        console.log({{ event_func_name }}.name, error);
         return Err(error);
     }
 }

+ 65 - 63
frontend/rust-lib/flowy-core/Cargo.toml

@@ -1,63 +1,65 @@
-[package]
-name = "flowy-core"
-version = "0.1.0"
-edition = "2018"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-lib-dispatch = { path = "../lib-dispatch" }
-lib-log = { path = "../lib-log" }
-flowy-user = { path = "../flowy-user" }
-flowy-net = { path = "../flowy-net" }
-flowy-folder = { path = "../flowy-folder" }
-flowy-database = { path = "../flowy-database" }
-database-model = { path = "../../../shared-lib/database-model" }
-user-model = { path = "../../../shared-lib/user-model" }
-flowy-client-ws = { path = "../../../shared-lib/flowy-client-ws" }
-flowy-sqlite = { path = "../flowy-sqlite", optional = true }
-flowy-document = { path = "../flowy-document" }
-flowy-revision = { path = "../flowy-revision" }
-flowy-error = { path = "../flowy-error", features = ["adaptor_ws"] }
-flowy-task = { path = "../flowy-task" }
-
-tracing = { version = "0.1", features = ["log"] }
-futures-core = { version = "0.3", default-features = false }
-bytes = "1.0"
-tokio = { version = "1", features = ["rt"] }
-parking_lot = "0.12.1"
-
-revision-model = { path = "../../../shared-lib/revision-model" }
-ws-model = { path = "../../../shared-lib/ws-model" }
-lib-ws = { path = "../../../shared-lib/lib-ws" }
-lib-infra = { path = "../../../shared-lib/lib-infra" }
-serde = "1.0"
-serde_json = "1.0"
-
-[features]
-default = ["rev-sqlite"]
-http_sync = ["flowy-folder/cloud_sync", "flowy-document/cloud_sync"]
-native_sync = ["flowy-folder/cloud_sync", "flowy-document/cloud_sync"]
-use_bunyan = ["lib-log/use_bunyan"]
-dart = [
-    "flowy-user/dart",
-    "flowy-net/dart",
-    "flowy-folder/dart",
-    "flowy-database/dart",
-    "flowy-document/dart",
-]
-ts = [
-    "flowy-user/ts",
-    "flowy-net/ts",
-    "flowy-folder/ts",
-    "flowy-database/ts",
-    "flowy-document/ts",
-]
-rev-sqlite = [
-    "flowy-sqlite",
-    "flowy-user/rev-sqlite",
-    "flowy-folder/rev-sqlite",
-    "flowy-database/rev-sqlite",
-    "flowy-document/rev-sqlite",
-]
-openssl_vendored = ["flowy-sqlite/openssl_vendored"]
+[package]
+name = "flowy-core"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+lib-dispatch = { path = "../lib-dispatch" }
+lib-log = { path = "../lib-log" }
+flowy-user = { path = "../flowy-user" }
+flowy-net = { path = "../flowy-net" }
+flowy-folder = { path = "../flowy-folder" }
+flowy-database = { path = "../flowy-database" }
+database-model = { path = "../../../shared-lib/database-model" }
+user-model = { path = "../../../shared-lib/user-model" }
+flowy-client-ws = { path = "../../../shared-lib/flowy-client-ws" }
+flowy-sqlite = { path = "../flowy-sqlite", optional = true }
+flowy-document = { path = "../flowy-document" }
+flowy-revision = { path = "../flowy-revision" }
+flowy-error = { path = "../flowy-error", features = ["adaptor_ws"] }
+flowy-task = { path = "../flowy-task" }
+
+tracing = { version = "0.1", features = ["log"] }
+futures-core = { version = "0.3", default-features = false }
+bytes = "1.4"
+tokio = { version = "1.26", features = ["full"] }
+console-subscriber = { version = "0.1.8", optional = true }
+parking_lot = "0.12.1"
+
+revision-model = { path = "../../../shared-lib/revision-model" }
+ws-model = { path = "../../../shared-lib/ws-model" }
+lib-ws = { path = "../../../shared-lib/lib-ws" }
+lib-infra = { path = "../../../shared-lib/lib-infra" }
+serde = "1.0"
+serde_json = "1.0"
+
+[features]
+default = ["rev-sqlite"]
+profiling = ["console-subscriber", "tokio/tracing"]
+http_sync = ["flowy-folder/cloud_sync", "flowy-document/cloud_sync"]
+native_sync = ["flowy-folder/cloud_sync", "flowy-document/cloud_sync"]
+use_bunyan = ["lib-log/use_bunyan"]
+dart = [
+    "flowy-user/dart",
+    "flowy-net/dart",
+    "flowy-folder/dart",
+    "flowy-database/dart",
+    "flowy-document/dart",
+]
+ts = [
+    "flowy-user/ts",
+    "flowy-net/ts",
+    "flowy-folder/ts",
+    "flowy-database/ts",
+    "flowy-document/ts",
+]
+rev-sqlite = [
+    "flowy-sqlite",
+    "flowy-user/rev-sqlite",
+    "flowy-folder/rev-sqlite",
+    "flowy-database/rev-sqlite",
+    "flowy-document/rev-sqlite",
+]
+openssl_vendored = ["flowy-sqlite/openssl_vendored"]

+ 10 - 0
frontend/rust-lib/flowy-core/src/lib.rs

@@ -102,6 +102,13 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
   filters.push(format!("dart_ffi={}", "info"));
   filters.push(format!("flowy_sqlite={}", "info"));
   filters.push(format!("flowy_net={}", "info"));
+
+  #[cfg(feature = "profiling")]
+  filters.push(format!("tokio={}", level));
+
+  #[cfg(feature = "profiling")]
+  filters.push(format!("runtime={}", level));
+
   filters.join(",")
 }
 
@@ -121,6 +128,9 @@ pub struct AppFlowyCore {
 
 impl AppFlowyCore {
   pub fn new(config: AppFlowyCoreConfig) -> Self {
+    #[cfg(feature = "profiling")]
+    console_subscriber::init();
+
     init_log(&config);
     init_kv(&config.storage_path);
     tracing::debug!("🔥 {:?}", config);

+ 14 - 14
frontend/rust-lib/flowy-database/Cargo.toml

@@ -24,28 +24,28 @@ anyhow = "1.0"
 strum = "0.21"
 strum_macros = "0.21"
 tracing = { version = "0.1", features = ["log"] }
-protobuf = {version = "2.18.0"}
-rust_decimal = "1.8.1"
-rusty-money = {version = "0.4.0", features = ["iso"]}
+protobuf = {version = "2.28.0"}
+rust_decimal = "1.28.1"
+rusty-money = {version = "0.4.1", features = ["iso"]}
 lazy_static = "1.4.0"
-chrono = "0.4.19"
+chrono = "0.4.23"
 nanoid = "0.4.0"
-bytes = { version = "1.0" }
+bytes = { version = "1.4" }
 diesel = {version = "1.4.8", features = ["sqlite"]}
 dashmap = "5"
-tokio = {version = "1", features = ["sync"]}
-rayon = "1.5.2"
+tokio = { version = "1.26", features = ["sync"]}
+rayon = "1.6.1"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = {version = "1.0"}
 serde_repr = "0.1"
-indexmap = {version = "1.9.1", features = ["serde"]}
+indexmap = {version = "1.9.2", features = ["serde"]}
 fancy-regex = "0.10.0"
-regex = "1.5.6"
+regex = "1.7.1"
 url = { version = "2"}
-futures = "0.3.15"
-atomic_refcell = "0.1.8"
-crossbeam-utils = "0.8.7"
-async-stream = "0.3.2"
+futures = "0.3.26"
+atomic_refcell = "0.1.9"
+crossbeam-utils = "0.8.15"
+async-stream = "0.3.4"
 parking_lot = "0.12.1"
 
 [dev-dependencies]
@@ -60,4 +60,4 @@ default = ["rev-sqlite"]
 rev-sqlite = ["flowy-sqlite"]
 dart = ["flowy-codegen/dart", "flowy-notification/dart"]
 ts = ["flowy-codegen/ts", "flowy-notification/ts"]
-flowy_unit_test = ["flowy-revision/flowy_unit_test"]
+flowy_unit_test = ["flowy-revision/flowy_unit_test"]

+ 1 - 1
frontend/rust-lib/flowy-database/src/entities/row_entities.rs

@@ -191,7 +191,7 @@ impl TryInto<CreateRowParams> for CreateRowPayloadPB {
   type Error = ErrorCode;
 
   fn try_into(self) -> Result<CreateRowParams, Self::Error> {
-    let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
+    let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
 
     Ok(CreateRowParams {
       view_id: view_id.0,

+ 2 - 2
frontend/rust-lib/flowy-database/src/event_handler.rs

@@ -534,8 +534,8 @@ pub(crate) async fn get_groups_handler(
 ) -> DataResult<RepeatedGroupPB, FlowyError> {
   let params: DatabaseViewIdPB = data.into_inner();
   let editor = manager.get_database_editor(&params.value).await?;
-  let group = editor.load_groups(&params.value).await?;
-  data_result_ok(group)
+  let groups = editor.load_groups(&params.value).await?;
+  data_result_ok(groups)
 }
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]

+ 15 - 18
frontend/rust-lib/flowy-database/src/manager.rs

@@ -118,7 +118,6 @@ impl DatabaseManager {
     Ok(())
   }
 
-  #[tracing::instrument(level = "debug", skip_all, err)]
   pub async fn create_database_block<T: AsRef<str>>(
     &self,
     block_id: T,
@@ -141,33 +140,33 @@ impl DatabaseManager {
       .await
   }
 
+  #[tracing::instrument(level = "debug", skip_all)]
   pub async fn close_database_view<T: AsRef<str>>(&self, view_id: T) -> FlowyResult<()> {
     let view_id = view_id.as_ref();
     let database_info = self.database_ref_indexer.get_database_with_view(view_id)?;
     tracing::Span::current().record("database_id", &database_info.database_id);
 
-    let mut should_remove_editor = false;
-    if let Some(database_editor) = self
+    // Create a temporary reference database_editor in case of holding the write lock
+    // of editors_by_database_id too long.
+    let database_editor = self
       .editors_by_database_id
       .write()
       .await
-      .get(&database_info.database_id)
-    {
+      .remove(&database_info.database_id);
+
+    if let Some(database_editor) = database_editor {
       database_editor.close_view_editor(view_id).await;
-      should_remove_editor = database_editor.number_of_ref_views().await == 0;
-      if should_remove_editor {
+      if database_editor.number_of_ref_views().await == 0 {
         database_editor.dispose().await;
+      } else {
+        self
+          .editors_by_database_id
+          .write()
+          .await
+          .insert(database_info.database_id, database_editor);
       }
     }
 
-    if should_remove_editor {
-      tracing::debug!("Close database base editor: {}", database_info.database_id);
-      self
-        .editors_by_database_id
-        .write()
-        .await
-        .remove(&database_info.database_id);
-    }
     Ok(())
   }
 
@@ -235,12 +234,10 @@ impl DatabaseManager {
     pool: Arc<ConnectionPool>,
   ) -> Result<Arc<DatabaseEditor>, FlowyError> {
     let user = self.database_user.clone();
-    tracing::debug!("Open database view: {}", view_id);
     let (base_view_pad, base_view_rev_manager) =
       make_database_view_revision_pad(view_id, user.clone()).await?;
     let mut database_id = base_view_pad.database_id.clone();
-
-    tracing::debug!("Open database: {}", database_id);
+    tracing::debug!("Open database: {} with view: {}", database_id, view_id);
     if database_id.is_empty() {
       // Before the database_id concept comes up, we used the view_id directly. So if
       // the database_id is empty, which means we can used the view_id. After the version 0.1.1,

+ 2 - 2
frontend/rust-lib/flowy-database/src/services/database/database_editor.rs

@@ -116,13 +116,13 @@ impl DatabaseEditor {
     self.database_views.open(view_editor).await
   }
 
-  #[tracing::instrument(name = "Close database editor view", level = "debug", skip_all)]
+  #[tracing::instrument(level = "debug", skip_all)]
   pub async fn close_view_editor(&self, view_id: &str) {
-    self.rev_manager.generate_snapshot().await;
     self.database_views.close(view_id).await;
   }
 
   pub async fn dispose(&self) {
+    self.rev_manager.generate_snapshot().await;
     self.database_blocks.close().await;
     self.rev_manager.close().await;
   }

+ 10 - 13
frontend/rust-lib/flowy-database/src/services/database_view/editor.rs

@@ -28,9 +28,7 @@ use flowy_error::FlowyResult;
 use flowy_revision::RevisionManager;
 use flowy_sqlite::ConnectionPool;
 use flowy_task::TaskDispatcher;
-use lib_infra::async_trait::async_trait;
 use lib_infra::future::Fut;
-use lib_infra::ref_map::RefCountValue;
 use nanoid::nanoid;
 use revision_model::Revision;
 use std::borrow::Cow;
@@ -85,6 +83,12 @@ pub struct DatabaseViewEditor {
   pub notifier: DatabaseViewChangedNotifier,
 }
 
+impl Drop for DatabaseViewEditor {
+  fn drop(&mut self) {
+    tracing::trace!("Drop {}", std::any::type_name::<Self>());
+  }
+}
+
 impl DatabaseViewEditor {
   pub async fn from_pad(
     user_id: &str,
@@ -178,12 +182,12 @@ impl DatabaseViewEditor {
     .await
   }
 
-  #[tracing::instrument(name = "close grid view editor", level = "trace", skip_all)]
+  #[tracing::instrument(name = "close database view editor", level = "trace", skip_all)]
   pub async fn close(&self) {
     self.rev_manager.generate_snapshot().await;
     self.rev_manager.close().await;
+    self.sort_controller.write().await.close().await;
     self.filter_controller.close().await;
-    self.sort_controller.read().await.close().await;
   }
 
   pub async fn handle_block_event(&self, event: Cow<'_, DatabaseBlockEvent>) {
@@ -509,8 +513,8 @@ impl DatabaseViewEditor {
         .did_receive_changes(SortChangeset::from_insert(sort_type))
         .await
     };
-    self.notify_did_update_sort(changeset).await;
     drop(sort_controller);
+    self.notify_did_update_sort(changeset).await;
     Ok(sort_rev)
   }
 
@@ -539,7 +543,7 @@ impl DatabaseViewEditor {
 
   pub async fn v_delete_all_sorts(&self) -> FlowyResult<()> {
     let all_sorts = self.v_get_all_sorts().await;
-    self.sort_controller.write().await.delete_all_sorts().await;
+    // self.sort_controller.write().await.delete_all_sorts().await;
     self
       .modify(|pad| {
         let changeset = pad.delete_all_sorts()?;
@@ -869,13 +873,6 @@ pub(crate) async fn get_cells_for_field(
   Ok(cells)
 }
 
-#[async_trait]
-impl RefCountValue for DatabaseViewEditor {
-  async fn did_remove(&self) {
-    self.close().await;
-  }
-}
-
 async fn new_group_controller(
   user_id: String,
   view_id: String,

+ 27 - 17
frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs

@@ -1,3 +1,4 @@
+#![allow(clippy::while_let_loop)]
 use crate::entities::{
   AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams,
   DeleteGroupParams, DeleteSortParams, InsertGroupParams, MoveGroupParams, RepeatedGroupPB, RowPB,
@@ -20,8 +21,8 @@ use flowy_error::FlowyResult;
 use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration};
 use flowy_sqlite::ConnectionPool;
 use lib_infra::future::Fut;
-use lib_infra::ref_map::RefCountHashMap;
 use std::borrow::Cow;
+use std::collections::HashMap;
 use std::sync::Arc;
 use tokio::sync::{broadcast, RwLock};
 
@@ -29,7 +30,7 @@ use tokio::sync::{broadcast, RwLock};
 pub struct DatabaseViews {
   user: Arc<dyn DatabaseUser>,
   delegate: Arc<dyn DatabaseViewData>,
-  view_editors: Arc<RwLock<RefCountHashMap<Arc<DatabaseViewEditor>>>>,
+  view_editors: Arc<RwLock<HashMap<String, Arc<DatabaseViewEditor>>>>,
   cell_data_cache: AtomicCellDataCache,
 }
 
@@ -40,7 +41,7 @@ impl DatabaseViews {
     cell_data_cache: AtomicCellDataCache,
     block_event_rx: broadcast::Receiver<DatabaseBlockEvent>,
   ) -> FlowyResult<Self> {
-    let view_editors = Arc::new(RwLock::new(RefCountHashMap::default()));
+    let view_editors = Arc::new(RwLock::new(HashMap::default()));
     listen_on_database_block_event(block_event_rx, view_editors.clone());
     Ok(Self {
       user,
@@ -60,7 +61,13 @@ impl DatabaseViews {
   }
 
   pub async fn close(&self, view_id: &str) {
-    self.view_editors.write().await.remove(view_id).await;
+    if let Ok(mut view_editors) = self.view_editors.try_write() {
+      if let Some(view_editor) = view_editors.remove(view_id) {
+        view_editor.close().await;
+      }
+    } else {
+      tracing::error!("Try to get the lock of view_editors failed");
+    }
   }
 
   pub async fn number_of_views(&self) -> usize {
@@ -269,7 +276,7 @@ impl DatabaseViews {
   pub async fn get_view_editor(&self, view_id: &str) -> FlowyResult<Arc<DatabaseViewEditor>> {
     debug_assert!(!view_id.is_empty());
     if let Some(editor) = self.view_editors.read().await.get(view_id) {
-      return Ok(editor);
+      return Ok(editor.clone());
     }
 
     tracing::trace!("{:p} create view:{} editor", self, view_id);
@@ -342,21 +349,24 @@ pub async fn make_database_view_rev_manager(
 
 fn listen_on_database_block_event(
   mut block_event_rx: broadcast::Receiver<DatabaseBlockEvent>,
-  view_editors: Arc<RwLock<RefCountHashMap<Arc<DatabaseViewEditor>>>>,
+  view_editors: Arc<RwLock<HashMap<String, Arc<DatabaseViewEditor>>>>,
 ) {
   tokio::spawn(async move {
     loop {
-      while let Ok(event) = block_event_rx.recv().await {
-        let read_guard = view_editors.read().await;
-        let view_editors = read_guard.values();
-        let event = if view_editors.len() == 1 {
-          Cow::Owned(event)
-        } else {
-          Cow::Borrowed(&event)
-        };
-        for view_editor in view_editors.iter() {
-          view_editor.handle_block_event(event.clone()).await;
-        }
+      match block_event_rx.recv().await {
+        Ok(event) => {
+          let read_guard = view_editors.read().await;
+          let view_editors = read_guard.values();
+          let event = if view_editors.len() == 1 {
+            Cow::Owned(event)
+          } else {
+            Cow::Borrowed(&event)
+          };
+          for view_editor in view_editors {
+            view_editor.handle_block_event(event.clone()).await;
+          }
+        },
+        Err(_) => break,
       }
     }
   });

+ 1 - 0
frontend/rust-lib/flowy-database/src/services/database_view/notifier.rs

@@ -1,3 +1,4 @@
+#![allow(clippy::while_let_loop)]
 use crate::entities::{ReorderAllRowsPB, ReorderSingleRowPB, RowsVisibilityChangesetPB};
 use crate::notification::{send_notification, DatabaseNotification};
 use crate::services::filter::FilterResultNotification;

+ 1 - 0
frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs

@@ -289,6 +289,7 @@ where
     decoded_field_type: &FieldType,
     field_rev: &FieldRevision,
   ) -> FlowyResult<BoxCellData> {
+    // tracing::debug!("get_cell_data: {:?}", std::any::type_name::<Self>());
     let cell_data = if self.transformable() {
       match self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev) {
         None => self.get_decoded_cell_data(cell_str, decoded_field_type, field_rev)?,

+ 11 - 6
frontend/rust-lib/flowy-database/src/services/filter/controller.rs

@@ -46,6 +46,12 @@ pub struct FilterController {
   notifier: DatabaseViewChangedNotifier,
 }
 
+impl Drop for FilterController {
+  fn drop(&mut self) {
+    tracing::trace!("Drop {}", std::any::type_name::<Self>());
+  }
+}
+
 impl FilterController {
   pub async fn new<T>(
     view_id: &str,
@@ -74,12 +80,11 @@ impl FilterController {
   }
 
   pub async fn close(&self) {
-    self
-      .task_scheduler
-      .write()
-      .await
-      .unregister_handler(&self.handler_id)
-      .await;
+    if let Ok(mut task_scheduler) = self.task_scheduler.try_write() {
+      task_scheduler.unregister_handler(&self.handler_id).await;
+    } else {
+      tracing::error!("Try to get the lock of task_scheduler failed");
+    }
   }
 
   #[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))]

+ 20 - 13
frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/block_impl.rs

@@ -22,7 +22,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
 
   fn create_revision_records(&self, revision_records: Vec<SyncRecord>) -> Result<(), Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
-    GridMetaRevisionSql::create(revision_records, &conn)?;
+    DatabaseBlockMetaRevisionSql::create(revision_records, &conn)?;
     Ok(())
   }
 
@@ -36,7 +36,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
     rev_ids: Option<Vec<i64>>,
   ) -> Result<Vec<SyncRecord>, Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
-    let records = GridMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
+    let records = DatabaseBlockMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
     Ok(records)
   }
 
@@ -47,7 +47,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
   ) -> Result<Vec<SyncRecord>, Self::Error> {
     let conn = &*self.pool.get().map_err(internal_error)?;
     let revisions =
-      GridMetaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
+      DatabaseBlockMetaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
     Ok(revisions)
   }
 
@@ -55,7 +55,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
     let conn = &*self.pool.get().map_err(internal_error)?;
     conn.immediate_transaction::<_, FlowyError, _>(|| {
       for changeset in changesets {
-        GridMetaRevisionSql::update(changeset, conn)?;
+        DatabaseBlockMetaRevisionSql::update(changeset, conn)?;
       }
       Ok(())
     })?;
@@ -68,7 +68,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
     rev_ids: Option<Vec<i64>>,
   ) -> Result<(), Self::Error> {
     let conn = &*self.pool.get().map_err(internal_error)?;
-    GridMetaRevisionSql::delete(object_id, rev_ids, conn)?;
+    DatabaseBlockMetaRevisionSql::delete(object_id, rev_ids, conn)?;
     Ok(())
   }
 
@@ -80,8 +80,8 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
   ) -> Result<(), Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
     conn.immediate_transaction::<_, FlowyError, _>(|| {
-      GridMetaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
-      GridMetaRevisionSql::create(inserted_records, &conn)?;
+      DatabaseBlockMetaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
+      DatabaseBlockMetaRevisionSql::create(inserted_records, &conn)?;
       Ok(())
     })
   }
@@ -96,8 +96,8 @@ impl SQLiteDatabaseBlockRevisionPersistence {
   }
 }
 
-struct GridMetaRevisionSql();
-impl GridMetaRevisionSql {
+struct DatabaseBlockMetaRevisionSql();
+impl DatabaseBlockMetaRevisionSql {
   fn create(revision_records: Vec<SyncRecord>, conn: &SqliteConnection) -> Result<(), FlowyError> {
     // Batch insert: https://diesel.rs/guides/all-about-inserts.html
 
@@ -105,7 +105,8 @@ impl GridMetaRevisionSql {
       .into_iter()
       .map(|record| {
         tracing::trace!(
-          "[GridMetaRevisionSql] create revision: {}:{:?}",
+          "[{}] create revision: {}:{:?}",
+          std::any::type_name::<Self>(),
           record.revision.object_id,
           record.revision.rev_id
         );
@@ -133,7 +134,8 @@ impl GridMetaRevisionSql {
       .filter(dsl::object_id.eq(changeset.object_id));
     let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
     tracing::debug!(
-      "[GridMetaRevisionSql] update revision:{} state:to {:?}",
+      "[{}] update revision:{} state:to {:?}",
+      std::any::type_name::<Self>(),
       changeset.rev_id,
       changeset.state
     );
@@ -193,7 +195,8 @@ impl GridMetaRevisionSql {
 
     if let Some(rev_ids) = rev_ids {
       tracing::trace!(
-        "[GridMetaRevisionSql] Delete revision: {}:{:?}",
+        "[{}] Delete revision: {}:{:?}",
+        std::any::type_name::<Self>(),
         object_id,
         rev_ids
       );
@@ -201,7 +204,11 @@ impl GridMetaRevisionSql {
     }
 
     let affected_row = sql.execute(conn)?;
-    tracing::trace!("[GridMetaRevisionSql] Delete {} rows", affected_row);
+    tracing::trace!(
+      "[{}] Delete {} rows",
+      std::any::type_name::<Self>(),
+      affected_row
+    );
     Ok(())
   }
 }

+ 11 - 4
frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/database_impl.rs

@@ -104,7 +104,8 @@ impl DatabaseRevisionSql {
       .into_iter()
       .map(|record| {
         tracing::trace!(
-          "[GridRevisionSql] create revision: {}:{:?}",
+          "[{}] create revision: {}:{:?}",
+          std::any::type_name::<Self>(),
           record.revision.object_id,
           record.revision.rev_id
         );
@@ -132,7 +133,8 @@ impl DatabaseRevisionSql {
       .filter(dsl::object_id.eq(changeset.object_id));
     let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
     tracing::debug!(
-      "[GridRevisionSql] update revision:{} state:to {:?}",
+      "[{}] update revision:{} state:to {:?}",
+      std::any::type_name::<Self>(),
       changeset.rev_id,
       changeset.state
     );
@@ -192,7 +194,8 @@ impl DatabaseRevisionSql {
 
     if let Some(rev_ids) = rev_ids {
       tracing::trace!(
-        "[GridRevisionSql] Delete revision: {}:{:?}",
+        "[{}] Delete revision: {}:{:?}",
+        std::any::type_name::<Self>(),
         object_id,
         rev_ids
       );
@@ -200,7 +203,11 @@ impl DatabaseRevisionSql {
     }
 
     let affected_row = sql.execute(conn)?;
-    tracing::trace!("[GridRevisionSql] Delete {} rows", affected_row);
+    tracing::trace!(
+      "[{}] Delete {} rows",
+      std::any::type_name::<Self>(),
+      affected_row
+    );
     Ok(())
   }
 }

+ 20 - 13
frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/view_impl.rs

@@ -31,7 +31,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
 
   fn create_revision_records(&self, revision_records: Vec<SyncRecord>) -> Result<(), Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
-    GridViewRevisionSql::create(revision_records, &conn)?;
+    DatabaseViewRevisionSql::create(revision_records, &conn)?;
     Ok(())
   }
 
@@ -45,7 +45,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
     rev_ids: Option<Vec<i64>>,
   ) -> Result<Vec<SyncRecord>, Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
-    let records = GridViewRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
+    let records = DatabaseViewRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
     Ok(records)
   }
 
@@ -56,7 +56,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
   ) -> Result<Vec<SyncRecord>, Self::Error> {
     let conn = &*self.pool.get().map_err(internal_error)?;
     let revisions =
-      GridViewRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
+      DatabaseViewRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
     Ok(revisions)
   }
 
@@ -64,7 +64,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
     let conn = &*self.pool.get().map_err(internal_error)?;
     conn.immediate_transaction::<_, FlowyError, _>(|| {
       for changeset in changesets {
-        GridViewRevisionSql::update(changeset, conn)?;
+        DatabaseViewRevisionSql::update(changeset, conn)?;
       }
       Ok(())
     })?;
@@ -77,7 +77,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
     rev_ids: Option<Vec<i64>>,
   ) -> Result<(), Self::Error> {
     let conn = &*self.pool.get().map_err(internal_error)?;
-    GridViewRevisionSql::delete(object_id, rev_ids, conn)?;
+    DatabaseViewRevisionSql::delete(object_id, rev_ids, conn)?;
     Ok(())
   }
 
@@ -89,22 +89,23 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
   ) -> Result<(), Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
     conn.immediate_transaction::<_, FlowyError, _>(|| {
-      GridViewRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
-      GridViewRevisionSql::create(inserted_records, &conn)?;
+      DatabaseViewRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
+      DatabaseViewRevisionSql::create(inserted_records, &conn)?;
       Ok(())
     })
   }
 }
 
-struct GridViewRevisionSql();
-impl GridViewRevisionSql {
+struct DatabaseViewRevisionSql();
+impl DatabaseViewRevisionSql {
   fn create(revision_records: Vec<SyncRecord>, conn: &SqliteConnection) -> Result<(), FlowyError> {
     // Batch insert: https://diesel.rs/guides/all-about-inserts.html
     let records = revision_records
       .into_iter()
       .map(|record| {
         tracing::trace!(
-          "[GridViewRevisionSql] create revision: {}:{:?}",
+          "[{}] create revision: {}:{:?}",
+          std::any::type_name::<Self>(),
           record.revision.object_id,
           record.revision.rev_id
         );
@@ -132,7 +133,8 @@ impl GridViewRevisionSql {
       .filter(dsl::object_id.eq(changeset.object_id));
     let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
     tracing::debug!(
-      "[GridViewRevisionSql] update revision:{} state:to {:?}",
+      "[{}] update revision:{} state:to {:?}",
+      std::any::type_name::<Self>(),
       changeset.rev_id,
       changeset.state
     );
@@ -192,7 +194,8 @@ impl GridViewRevisionSql {
 
     if let Some(rev_ids) = rev_ids {
       tracing::trace!(
-        "[GridViewRevisionSql] Delete revision: {}:{:?}",
+        "[{}] Delete revision: {}:{:?}",
+        std::any::type_name::<Self>(),
         object_id,
         rev_ids
       );
@@ -200,7 +203,11 @@ impl GridViewRevisionSql {
     }
 
     let affected_row = sql.execute(conn)?;
-    tracing::trace!("[GridViewRevisionSql] Delete {} rows", affected_row);
+    tracing::trace!(
+      "[{}] Delete {} rows",
+      std::any::type_name::<Self>(),
+      affected_row
+    );
     Ok(())
   }
 }

+ 11 - 6
frontend/rust-lib/flowy-database/src/services/sort/controller.rs

@@ -37,6 +37,12 @@ pub struct SortController {
   notifier: DatabaseViewChangedNotifier,
 }
 
+impl Drop for SortController {
+  fn drop(&mut self) {
+    tracing::trace!("Drop {}", std::any::type_name::<Self>());
+  }
+}
+
 impl SortController {
   pub fn new<T>(
     view_id: &str,
@@ -63,12 +69,11 @@ impl SortController {
   }
 
   pub async fn close(&self) {
-    self
-      .task_scheduler
-      .write()
-      .await
-      .unregister_handler(&self.handler_id)
-      .await;
+    if let Ok(mut task_scheduler) = self.task_scheduler.try_write() {
+      task_scheduler.unregister_handler(&self.handler_id).await;
+    } else {
+      tracing::error!("Try to get the lock of task_scheduler failed");
+    }
   }
 
   pub async fn did_receive_row_changed(&self, row_id: &str) {

+ 5 - 5
frontend/rust-lib/flowy-derive/Cargo.toml

@@ -14,7 +14,7 @@ name = "tests"
 path = "tests/progress.rs"
 
 [dependencies]
-syn = { version = "1.0.60", features = ["extra-traits", "visit"] }
+syn = { version = "1.0.109", features = ["extra-traits", "visit"] }
 quote = "1.0"
 proc-macro2 = "1.0"
 flowy-ast = { path = "../flowy-ast" }
@@ -22,9 +22,9 @@ lazy_static = {version = "1.4.0"}
 dashmap = "5"
 flowy-codegen = { path = "../flowy-codegen"}
 serde_json = "1.0"
-walkdir = "2.3.1"
+walkdir = "2.3.2"
 
 [dev-dependencies]
-tokio = { version = "1", features = ["full"] }
-trybuild = "1.0.40"
-log = "0.4.11"
+tokio = { version = "1.26", features = ["full"] }
+trybuild = "1.0.77"
+log = "0.4.17"

+ 10 - 10
frontend/rust-lib/flowy-document/Cargo.toml

@@ -25,29 +25,29 @@ flowy-notification = { path = "../flowy-notification" }
 
 diesel = {version = "1.4.8", features = ["sqlite"]}
 diesel_derives = {version = "1.4.1", features = ["sqlite"]}
-protobuf = {version = "2.18.0"}
-tokio = {version = "1", features = ["sync"]}
+protobuf = {version = "2.28.0"}
+tokio = { version = "1.26", features = ["sync"]}
 tracing = { version = "0.1", features = ["log"] }
 
-bytes = { version = "1.1" }
+bytes = { version = "1.4" }
 md5 = "0.7.0"
 strum = "0.21"
 strum_macros = "0.21"
 dashmap = "5"
-url = "2.2"
+url = "2.3"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = {version = "1.0"}
-chrono = "0.4.19"
-futures-util = "0.3.15"
-async-stream = "0.3.2"
-futures = "0.3.15"
+chrono = "0.4.23"
+futures-util = "0.3.26"
+async-stream = "0.3.4"
+futures = "0.3.26"
 
 [dev-dependencies]
 flowy-test = { path = "../flowy-test" }
 flowy-document = { path = "../flowy-document", features = ["flowy_unit_test"]}
 derive_more = {version = "0.99", features = ["display"]}
-tracing-subscriber = "0.2.0"
-unicode-segmentation = "1.8"
+tracing-subscriber = "0.2.25"
+unicode-segmentation = "1.10"
 
 color-eyre = { version = "0.5", default-features = false }
 criterion = "0.3"

+ 1 - 0
frontend/rust-lib/flowy-document/src/editor/queue.rs

@@ -1,3 +1,4 @@
+#![allow(clippy::while_let_loop)]
 use crate::editor::document::Document;
 use crate::DocumentUser;
 use async_stream::stream;

+ 1 - 0
frontend/rust-lib/flowy-document/src/old_editor/queue.rs

@@ -1,3 +1,4 @@
+#![allow(clippy::while_let_loop)]
 use crate::old_editor::web_socket::DeltaDocumentResolveOperations;
 use crate::DocumentUser;
 use async_stream::stream;

+ 1 - 0
frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_snapshot.rs

@@ -1,3 +1,4 @@
+#![allow(clippy::unused_unit)]
 use bytes::Bytes;
 use flowy_error::{internal_error, FlowyResult};
 use flowy_revision::{RevisionSnapshotData, RevisionSnapshotPersistence};

+ 3 - 3
frontend/rust-lib/flowy-error/Cargo.toml

@@ -7,8 +7,8 @@ edition = "2018"
 
 [dependencies]
 flowy-derive = { path = "../flowy-derive" }
-protobuf = {version = "2.20.0"}
-bytes = "1.0"
+protobuf = {version = "2.28.0"}
+bytes = "1.4"
 anyhow = "1.0"
 thiserror = "1.0"
 
@@ -20,7 +20,7 @@ flowy-client-ws = { path = "../../../shared-lib/flowy-client-ws", optional = tru
 serde_json = {version = "1.0", optional = true}
 serde_repr = { version = "0.1" }
 serde = "1.0"
-reqwest = { version = "0.11", optional = true }
+reqwest = { version = "0.11.14", optional = true }
 http-error-code = { git = "https://github.com/AppFlowy-IO/AppFlowy-Server", branch = "refactor/appflowy_server", optional = true }
 flowy-sqlite = { path = "../flowy-sqlite", optional = true}
 r2d2 = { version = "0.8", optional = true}

+ 6 - 6
frontend/rust-lib/flowy-folder/Cargo.toml

@@ -23,20 +23,20 @@ flowy-revision = { path = "../flowy-revision" }
 flowy-revision-persistence = { path = "../flowy-revision-persistence" }
 
 parking_lot = "0.12.1"
-protobuf = {version = "2.18.0"}
-log = "0.4.14"
+protobuf = {version = "2.28.0"}
+log = "0.4.17"
 diesel = {version = "1.4.8", features = ["sqlite"]}
 diesel_derives = {version = "1.4.1", features = ["sqlite"]}
-futures = "0.3.15"
+futures = "0.3.26"
 pin-project = "1.0"
 strum = "0.21"
 strum_macros = "0.21"
-tokio = { version = "1", features = ["rt"] }
+tokio = { version = "1.26", features = ["rt"] }
 lazy_static = "1.4.0"
 serde = { version = "1.0", features = ["derive"] }
 tracing = { version = "0.1", features = ["log"] }
-bytes = { version = "1.0" }
-unicode-segmentation = "1.8"
+bytes = { version = "1.4" }
+unicode-segmentation = "1.10"
 serde_json = "1.0"
 
 [dev-dependencies]

+ 11 - 4
frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs

@@ -106,7 +106,8 @@ impl FolderRevisionSql {
       .into_iter()
       .map(|record| {
         tracing::trace!(
-          "[TextRevisionSql] create revision: {}:{:?}",
+          "[{}] create revision: {}:{:?}",
+          std::any::type_name::<Self>(),
           record.revision.object_id,
           record.revision.rev_id
         );
@@ -135,7 +136,8 @@ impl FolderRevisionSql {
       .filter(dsl::doc_id.eq(changeset.object_id));
     let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
     tracing::debug!(
-      "[TextRevisionSql] update revision:{} state:to {:?}",
+      "[{}] update revision:{} state:to {:?}",
+      std::any::type_name::<Self>(),
       changeset.rev_id,
       changeset.state
     );
@@ -193,7 +195,8 @@ impl FolderRevisionSql {
 
     if let Some(rev_ids) = rev_ids {
       tracing::trace!(
-        "[TextRevisionSql] Delete revision: {}:{:?}",
+        "[{}] Delete revision: {}:{:?}",
+        std::any::type_name::<Self>(),
         object_id,
         rev_ids
       );
@@ -201,7 +204,11 @@ impl FolderRevisionSql {
     }
 
     let affected_row = sql.execute(conn)?;
-    tracing::trace!("[TextRevisionSql] Delete {} rows", affected_row);
+    tracing::trace!(
+      "[{}] Delete {} rows",
+      std::any::type_name::<Self>(),
+      affected_row
+    );
     Ok(())
   }
 }

+ 0 - 3
frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs

@@ -179,7 +179,6 @@ impl WorkspaceController {
 }
 
 impl WorkspaceController {
-  #[tracing::instrument(level = "trace", skip(self), err)]
   async fn create_workspace_on_server(
     &self,
     params: CreateWorkspaceParams,
@@ -188,7 +187,6 @@ impl WorkspaceController {
     self.cloud_service.create_workspace(&token, params).await
   }
 
-  #[tracing::instrument(level = "trace", skip(self), err)]
   fn update_workspace_on_server(&self, params: UpdateWorkspaceParams) -> Result<(), FlowyError> {
     let (token, server) = (self.user.token()?, self.cloud_service.clone());
     tokio::spawn(async move {
@@ -203,7 +201,6 @@ impl WorkspaceController {
     Ok(())
   }
 
-  #[tracing::instrument(level = "trace", skip(self), err)]
   fn delete_workspace_on_server(&self, workspace_id: &str) -> Result<(), FlowyError> {
     let params = WorkspaceIdPB {
       value: Some(workspace_id.to_string()),

+ 7 - 7
frontend/rust-lib/flowy-net/Cargo.toml

@@ -24,24 +24,24 @@ flowy-user = { path = "../flowy-user" }
 flowy-document = { path = "../flowy-document" }
 lazy_static = "1.4.0"
 lib-infra = { path = "../../../shared-lib/lib-infra" }
-protobuf = {version = "2.18.0"}
+protobuf = {version = "2.28.0"}
 lib-ws = { path = "../../../shared-lib/lib-ws" }
-bytes = { version = "1.0" }
+bytes = { version = "1.4" }
 anyhow = "1.0"
-tokio = {version = "1", features = ["sync"]}
+tokio = { version = "1.26", features = ["sync"]}
 parking_lot = "0.12.1"
 strum = "0.21"
 strum_macros = "0.21"
 tracing = { version = "0.1", features = ["log"] }
 dashmap = "5"
-async-stream = "0.3.2"
-futures-util = "0.3.15"
-reqwest = "0.11"
+async-stream = "0.3.4"
+futures-util = "0.3.26"
+reqwest = "0.11.14"
 hyper = "0.14"
 config = { version = "0.10.1", default-features = false, features = ["yaml"] }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
-serde-aux = "1.0.1"
+serde-aux = "1.1.0"
 nanoid = "0.4.0"
 thiserror = "1.0"
 

+ 2 - 2
frontend/rust-lib/flowy-notification/Cargo.toml

@@ -7,9 +7,9 @@ edition = "2018"
 
 [dependencies]
 lazy_static = { version = "1.4.0" }
-protobuf = { version = "2.20.0" }
+protobuf = { version = "2.28.0" }
 tracing = { version = "0.1", features = ["log"] }
-bytes = { version = "1.0" }
+bytes = { version = "1.4" }
 serde = "1.0"
 
 flowy-derive = { path = "../flowy-derive" }

+ 6 - 6
frontend/rust-lib/flowy-revision/Cargo.toml

@@ -13,15 +13,15 @@ lib-infra = { path = "../../../shared-lib/lib-infra" }
 flowy-error = { path = "../flowy-error" }
 flowy-revision-persistence= { path = "../flowy-revision-persistence" }
 tracing = { version = "0.1", features = ["log"] }
-tokio = {version = "1", features = ["sync"]}
-bytes = { version = "1.1" }
+tokio = { version = "1.26", features = ["sync"]}
+bytes = { version = "1.4" }
 strum = "0.21"
 strum_macros = "0.21"
 dashmap = "5"
 serde = { version = "1.0", features = ["derive"] }
-futures-util = "0.3.15"
-futures = "0.3.15"
-async-stream = "0.3.2"
+futures-util = "0.3.26"
+futures = "0.3.26"
+async-stream = "0.3.4"
 serde_json = {version = "1.0"}
 
 [dev-dependencies]
@@ -32,4 +32,4 @@ serde_json = { version = "1.0" }
 parking_lot = "0.12.1"
 
 [features]
-flowy_unit_test = []
+flowy_unit_test = []

+ 1 - 0
frontend/rust-lib/flowy-revision/src/rev_queue.rs

@@ -1,3 +1,4 @@
+#![allow(clippy::while_let_loop)]
 use crate::{RevIdCounter, RevisionMergeable, RevisionPersistence};
 use async_stream::stream;
 use bytes::Bytes;

+ 23 - 23
frontend/rust-lib/flowy-sqlite/Cargo.toml

@@ -1,23 +1,23 @@
-[package]
-name = "flowy-sqlite"
-version = "0.1.0"
-edition = "2018"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-diesel = { version = "1.4.8", features = ["sqlite"] }
-diesel_derives = { version = "1.4.1", features = ["sqlite"] }
-diesel_migrations = { version = "1.4.0", features = ["sqlite"] }
-tracing = { version = "0.1", features = ["log"] }
-lazy_static = "1.4.0"
-
-r2d2 = "0.8.9"
-libsqlite3-sys = { version = ">=0.8.0, <0.24.0", features = ["bundled"] }
-scheduled-thread-pool = "0.2.5"
-error-chain = "=0.12.0"
-openssl = { version = "0.10.38", optional = true, features = ["vendored"] }
-openssl-sys = { version = "0.9.69", optional = true, features = ["vendored"] }
-
-[features]
-openssl_vendored = ["openssl", "openssl-sys"]
+[package]
+name = "flowy-sqlite"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+diesel = { version = "1.4.8", features = ["sqlite"] }
+diesel_derives = { version = "1.4.1", features = ["sqlite"] }
+diesel_migrations = { version = "1.4.0", features = ["sqlite"] }
+tracing = { version = "0.1", features = ["log"] }
+lazy_static = "1.4.0"
+
+r2d2 = "0.8.10"
+libsqlite3-sys = { version = ">=0.8.0, <0.24.0", features = ["bundled"] }
+scheduled-thread-pool = "0.2.6"
+error-chain = "=0.12.0"
+openssl = { version = "0.10.45", optional = true, features = ["vendored"] }
+openssl-sys = { version = "0.9.80", optional = true, features = ["vendored"] }
+
+[features]
+openssl_vendored = ["openssl", "openssl-sys"]

+ 3 - 3
frontend/rust-lib/flowy-task/Cargo.toml

@@ -7,11 +7,11 @@ edition = "2021"
 
 [dependencies]
 lib-infra = { path = "../../../shared-lib/lib-infra" }
-tokio = {version = "1", features = ["sync", "macros", ]}
-atomic_refcell = "0.1.8"
+tokio = { version = "1.26", features = ["sync", "macros", ]}
+atomic_refcell = "0.1.9"
 anyhow = "1.0"
 tracing = { version = "0.1", features = ["log"] }
 
 [dev-dependencies]
 rand = "0.8.5"
-futures = "0.3.15"
+futures = "0.3.26"

+ 21 - 31
frontend/rust-lib/flowy-task/src/scheduler.rs

@@ -2,9 +2,10 @@ use crate::queue::TaskQueue;
 use crate::store::TaskStore;
 use crate::{Task, TaskContent, TaskId, TaskState};
 use anyhow::Error;
-use lib_infra::async_trait::async_trait;
+
 use lib_infra::future::BoxResultFuture;
-use lib_infra::ref_map::{RefCountHashMap, RefCountValue};
+
+use std::collections::HashMap;
 use std::sync::Arc;
 use std::time::Duration;
 
@@ -15,7 +16,7 @@ pub struct TaskDispatcher {
   queue: TaskQueue,
   store: TaskStore,
   timeout: Duration,
-  handlers: RefCountHashMap<RefCountTaskHandler>,
+  handlers: HashMap<String, Arc<dyn TaskHandler>>,
 
   notifier: watch::Sender<bool>,
   pub(crate) notifier_rx: Option<watch::Receiver<bool>>,
@@ -28,7 +29,7 @@ impl TaskDispatcher {
       queue: TaskQueue::new(),
       store: TaskStore::new(),
       timeout,
-      handlers: RefCountHashMap::new(),
+      handlers: HashMap::new(),
       notifier,
       notifier_rx: Some(notifier_rx),
     }
@@ -39,13 +40,17 @@ impl TaskDispatcher {
     T: TaskHandler,
   {
     let handler_id = handler.handler_id().to_owned();
-    self
-      .handlers
-      .insert(handler_id, RefCountTaskHandler(Arc::new(handler)));
+    self.handlers.insert(handler_id, Arc::new(handler));
   }
 
   pub async fn unregister_handler<T: AsRef<str>>(&mut self, handler_id: T) {
-    self.handlers.remove(handler_id.as_ref()).await;
+    if let Some(handler) = self.handlers.remove(handler_id.as_ref()) {
+      tracing::trace!(
+        "{}:{} is unregistered",
+        handler.handler_name(),
+        handler.handler_id()
+      );
+    }
   }
 
   pub fn stop(&mut self) {
@@ -69,25 +74,25 @@ impl TaskDispatcher {
     let content = task.content.take()?;
     if let Some(handler) = self.handlers.get(&task.handler_id) {
       task.set_state(TaskState::Processing);
-      tracing::trace!(
-        "Run {} task with content: {:?}",
-        handler.handler_name(),
-        content
-      );
+      tracing::trace!("{} task is running", handler.handler_name(),);
       match tokio::time::timeout(self.timeout, handler.run(content)).await {
         Ok(result) => match result {
-          Ok(_) => task.set_state(TaskState::Done),
+          Ok(_) => {
+            tracing::trace!("{} task is done", handler.handler_name(),);
+            task.set_state(TaskState::Done)
+          },
           Err(e) => {
-            tracing::error!("Process {} task failed: {:?}", handler.handler_name(), e);
+            tracing::error!("{} task is failed: {:?}", handler.handler_name(), e);
             task.set_state(TaskState::Failure);
           },
         },
         Err(e) => {
-          tracing::error!("Process {} task timeout: {:?}", handler.handler_name(), e);
+          tracing::error!("{} task is timeout: {:?}", handler.handler_name(), e);
           task.set_state(TaskState::Timeout);
         },
       }
     } else {
+      tracing::trace!("{} is cancel", task.handler_id);
       task.set_state(TaskState::Cancel);
     }
     let _ = ret.send(task.into());
@@ -197,18 +202,3 @@ where
     (**self).run(content)
   }
 }
-#[derive(Clone)]
-struct RefCountTaskHandler(Arc<dyn TaskHandler>);
-
-#[async_trait]
-impl RefCountValue for RefCountTaskHandler {
-  async fn did_remove(&self) {}
-}
-
-impl std::ops::Deref for RefCountTaskHandler {
-  type Target = Arc<dyn TaskHandler>;
-
-  fn deref(&self) -> &Self::Target {
-    &self.0
-  }
-}

+ 8 - 8
frontend/rust-lib/flowy-test/Cargo.toml

@@ -19,21 +19,21 @@ lib-infra = { path = "../../../shared-lib/lib-infra" }
 
 serde = { version = "1.0", features = ["derive"] }
 serde_json = {version = "1.0"}
-protobuf = {version = "2.24.1"}
-claim = "0.5.0"
-tokio = { version = "1", features = ["full"]}
-futures-util = "0.3.15"
+protobuf = {version = "2.28.0"}
+#claim = "0.5.0"
+tokio = { version = "1.26", features = ["full"]}
+futures-util = "0.3.26"
 thread-id = "3.3.0"
 log = "0.4"
-bytes = "1.0"
+bytes = "1.4"
 nanoid = "0.4.0"
 
 [dev-dependencies]
 quickcheck = "1.0.3"
 quickcheck_macros = "0.9.1"
-fake = "2.4.3"
-futures = "0.3.15"
+fake = "2.5.0"
+futures = "0.3.26"
 serial_test = "0.5.1"
 
 [features]
-dart = ["flowy-core/dart"]
+dart = ["flowy-core/dart"]

+ 5 - 5
frontend/rust-lib/flowy-user/Cargo.toml

@@ -15,19 +15,19 @@ flowy-notification = { path = "../flowy-notification" }
 lib-dispatch = { path = "../lib-dispatch" }
 
 tracing = { version = "0.1", features = ["log"] }
-bytes = "1.0"
+bytes = "1.4"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = {version = "1.0"}
-log = "0.4.14"
-protobuf = {version = "2.18.0"}
+log = "0.4.17"
+protobuf = {version = "2.28.0"}
 lazy_static = "1.4.0"
 diesel = {version = "1.4.8", features = ["sqlite"]}
 diesel_derives = {version = "1.4.1", features = ["sqlite"]}
-once_cell = "1.7.2"
+once_cell = "1.17.1"
 parking_lot = "0.12.1"
 strum = "0.21"
 strum_macros = "0.21"
-tokio = { version = "1", features = ["rt"] }
+tokio = { version = "1.26", features = ["rt"] }
 
 [dev-dependencies]
 flowy-test = { path = "../flowy-test" }

+ 9 - 9
frontend/rust-lib/lib-dispatch/Cargo.toml

@@ -8,13 +8,13 @@ edition = "2018"
 [dependencies]
 pin-project = "1.0"
 futures-core = { version = "0.3", default-features = false }
-futures-channel = "0.3.15"
-futures = "0.3.15"
-futures-util = "0.3.15"
-bytes = {version = "1.0", features = ["serde"]}
-tokio = { version = "1", features = ["full"] }
+futures-channel = "0.3.26"
+futures = "0.3.26"
+futures-util = "0.3.26"
+bytes = {version = "1.4", features = ["serde"]}
+tokio = { version = "1.26", features = ["full"] }
 nanoid = "0.4.0"
-log = "0.4.14"
+log = "0.4.17"
 thread-id = "3.3.0"
 dyn-clone = "1.0"
 derivative = "2.2.0"
@@ -24,12 +24,12 @@ serde_repr = { version = "0.1", optional = true }
 
 #optional crate
 bincode = { version = "1.3", optional = true}
-protobuf = {version = "2.24.1", optional = true}
+protobuf = {version = "2.28.0", optional = true}
 tracing = { version = "0.1"}
 
 [dev-dependencies]
-tokio = { version = "1", features = ["full"] }
-futures-util = "0.3.15"
+tokio = { version = "1.26", features = ["full"] }
+futures-util = "0.3.26"
 
 [features]
 default = ["use_protobuf"]

+ 5 - 5
frontend/rust-lib/lib-log/Cargo.toml

@@ -7,17 +7,17 @@ edition = "2018"
 
 [dependencies]
 
-tracing-log = { version = "0.1.1"}
-tracing-subscriber = { version = "0.2.12", features = ["registry", "env-filter", "ansi", "json"] }
-tracing-bunyan-formatter = "0.2.2"
+tracing-log = { version = "0.1.3"}
+tracing-subscriber = { version = "0.2.25", features = ["registry", "env-filter", "ansi", "json"] }
+tracing-bunyan-formatter = "0.2.6"
 tracing-appender = "0.1"
 tracing-core = "0.1"
 tracing = { version = "0.1", features = ["log"] }
-log = "0.4.14"
+log = "0.4.17"
 serde_json = "1.0"
 serde = "1.0"
 chrono = "0.4"
 lazy_static = "1.4.0"
 
 [features]
-use_bunyan = []
+use_bunyan = []

+ 353 - 117
shared-lib/Cargo.lock

@@ -11,6 +11,15 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.5.2"
@@ -19,19 +28,20 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
 
 [[package]]
 name = "async-stream"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
+checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e"
 dependencies = [
  "async-stream-impl",
  "futures-core",
+ "pin-project-lite",
 ]
 
 [[package]]
 name = "async-stream-impl"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
+checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -40,9 +50,9 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.59"
+version = "0.1.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"
+checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -62,9 +72,9 @@ dependencies = [
 
 [[package]]
 name = "autocfg"
-version = "1.0.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "base64"
@@ -102,6 +112,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bumpalo"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+
 [[package]]
 name = "byteorder"
 version = "1.4.3"
@@ -110,9 +126,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "bytes"
-version = "1.1.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
 
 [[package]]
 name = "cfg-if"
@@ -122,24 +144,27 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "chrono"
-version = "0.4.19"
+version = "0.4.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
 dependencies = [
- "libc",
+ "iana-time-zone",
+ "js-sys",
  "num-integer",
  "num-traits",
  "time",
+ "wasm-bindgen",
  "winapi",
 ]
 
 [[package]]
-name = "claim"
-version = "0.4.0"
+name = "codespan-reporting"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68ad37958d55b29a7088909368968d2fe876a24c203f8441195130f3b15194b9"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
 dependencies = [
- "autocfg",
+ "termcolor",
+ "unicode-width",
 ]
 
 [[package]]
@@ -154,6 +179,12 @@ dependencies = [
  "yaml-rust",
 ]
 
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
 [[package]]
 name = "cpufeatures"
 version = "0.2.1"
@@ -163,6 +194,50 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "cxx"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "dashmap"
 version = "5.2.0"
@@ -318,19 +393,18 @@ dependencies = [
 
 [[package]]
 name = "form_urlencoded"
-version = "1.0.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
 dependencies = [
- "matches",
  "percent-encoding",
 ]
 
 [[package]]
 name = "futures"
-version = "0.3.17"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca"
+checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -343,9 +417,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.17"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -353,15 +427,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.17"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.17"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -370,18 +444,16 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.17"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
+checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.17"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
 dependencies = [
- "autocfg",
- "proc-macro-hack",
  "proc-macro2",
  "quote",
  "syn",
@@ -389,23 +461,22 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.17"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
 
 [[package]]
 name = "futures-task"
-version = "0.3.17"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
 
 [[package]]
 name = "futures-util"
-version = "0.3.17"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
 dependencies = [
- "autocfg",
  "futures-channel",
  "futures-core",
  "futures-io",
@@ -415,8 +486,6 @@ dependencies = [
  "memchr",
  "pin-project-lite",
  "pin-utils",
- "proc-macro-hack",
- "proc-macro-nested",
  "slab",
 ]
 
@@ -438,7 +507,7 @@ checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
 dependencies = [
  "cfg-if",
  "libc",
- "wasi",
+ "wasi 0.10.0+wasi-snapshot-preview1",
 ]
 
 [[package]]
@@ -488,6 +557,30 @@ version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
 [[package]]
 name = "idna"
 version = "0.2.3"
@@ -499,11 +592,21 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
 [[package]]
 name = "indexmap"
-version = "1.9.1"
+version = "1.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
 dependencies = [
  "autocfg",
  "hashbrown",
@@ -512,9 +615,9 @@ dependencies = [
 
 [[package]]
 name = "indextree"
-version = "4.4.0"
+version = "4.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42b4b46b3311ebd8e5cd44f6b03b36e0f48a70552cf6b036afcebc5626794066"
+checksum = "497f036ac2fae75c34224648a77802e5dd4e9cfb56f4713ab6b12b7160a0523b"
 
 [[package]]
 name = "instant"
@@ -531,6 +634,15 @@ version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
 
+[[package]]
+name = "js-sys"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+dependencies = [
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -609,9 +721,18 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.107"
+version = "0.2.139"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
+dependencies = [
+ "cc",
+]
 
 [[package]]
 name = "linked-hash-map"
@@ -630,9 +751,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.14"
+version = "0.4.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
 dependencies = [
  "cfg-if",
 ]
@@ -657,24 +778,14 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
 
 [[package]]
 name = "mio"
-version = "0.7.14"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
 dependencies = [
  "libc",
  "log",
- "miow",
- "ntapi",
- "winapi",
-]
-
-[[package]]
-name = "miow"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
-dependencies = [
- "winapi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.45.0",
 ]
 
 [[package]]
@@ -697,15 +808,6 @@ dependencies = [
  "version_check",
 ]
 
-[[package]]
-name = "ntapi"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
-dependencies = [
- "winapi",
-]
-
 [[package]]
 name = "num-integer"
 version = "0.1.44"
@@ -737,9 +839,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.8.0"
+version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
 
 [[package]]
 name = "opaque-debug"
@@ -792,14 +894,14 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-sys",
+ "windows-sys 0.36.1",
 ]
 
 [[package]]
 name = "percent-encoding"
-version = "2.1.0"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
 
 [[package]]
 name = "pin-project"
@@ -839,18 +941,6 @@ version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
 
-[[package]]
-name = "proc-macro-hack"
-version = "0.5.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
-
-[[package]]
-name = "proc-macro-nested"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.47"
@@ -862,9 +952,9 @@ dependencies = [
 
 [[package]]
 name = "protobuf"
-version = "2.25.2"
+version = "2.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47c327e191621a2158159df97cdbc2e7074bb4e940275e35abf38eb3d2595754"
+checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
 
 [[package]]
 name = "quickcheck"
@@ -920,9 +1010,9 @@ dependencies = [
 
 [[package]]
 name = "rand_core"
-version = "0.6.3"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
  "getrandom",
 ]
@@ -975,6 +1065,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
+[[package]]
+name = "scratch"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
+
 [[package]]
 name = "serde"
 version = "1.0.136"
@@ -1084,6 +1180,16 @@ version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
 
+[[package]]
+name = "socket2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
@@ -1155,7 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
 dependencies = [
  "libc",
- "wasi",
+ "wasi 0.10.0+wasi-snapshot-preview1",
  "winapi",
 ]
 
@@ -1176,9 +1282,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.14.0"
+version = "1.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
+checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
 dependencies = [
  "autocfg",
  "bytes",
@@ -1186,19 +1292,19 @@ dependencies = [
  "memchr",
  "mio",
  "num_cpus",
- "once_cell",
- "parking_lot 0.11.2",
+ "parking_lot 0.12.1",
  "pin-project-lite",
  "signal-hook-registry",
+ "socket2",
  "tokio-macros",
- "winapi",
+ "windows-sys 0.45.0",
 ]
 
 [[package]]
 name = "tokio-macros"
-version = "1.6.0"
+version = "1.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
+checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1299,19 +1405,24 @@ dependencies = [
 
 [[package]]
 name = "unicode-segmentation"
-version = "1.8.0"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
 [[package]]
 name = "url"
-version = "2.2.2"
+version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
 dependencies = [
  "form_urlencoded",
- "idna",
- "matches",
+ "idna 0.3.0",
  "percent-encoding",
 ]
 
@@ -1319,7 +1430,6 @@ dependencies = [
 name = "user-model"
 version = "0.1.0"
 dependencies = [
- "claim",
  "fake",
  "fancy-regex",
  "futures",
@@ -1350,7 +1460,7 @@ version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32ad5bf234c7d3ad1042e5252b7eddb2c4669ee23f32c7dd0e9b7705f07ef591"
 dependencies = [
- "idna",
+ "idna 0.2.3",
  "lazy_static",
  "regex",
  "serde",
@@ -1371,6 +1481,66 @@ version = "0.10.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -1408,43 +1578,109 @@ version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
 dependencies = [
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_msvc",
+ "windows_aarch64_msvc 0.36.1",
+ "windows_i686_gnu 0.36.1",
+ "windows_i686_msvc 0.36.1",
+ "windows_x86_64_gnu 0.36.1",
+ "windows_x86_64_msvc 0.36.1",
 ]
 
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc 0.42.1",
+ "windows_i686_gnu 0.42.1",
+ "windows_i686_msvc 0.42.1",
+ "windows_x86_64_gnu 0.42.1",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc 0.42.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+
 [[package]]
 name = "ws-model"
 version = "0.1.0"

+ 2 - 2
shared-lib/database-model/Cargo.toml

@@ -6,9 +6,9 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-bytes = "1.0"
+bytes = "1.4"
 serde = { version = "1.0", features = ["derive", "rc"] }
 serde_json = {version = "1.0"}
 serde_repr = "0.1"
 nanoid = "0.4.0"
-indexmap = {version = "1.9.1", features = ["serde"]}
+indexmap = {version = "1.9.2", features = ["serde"]}

+ 1 - 1
shared-lib/flowy-client-network-config/Cargo.toml

@@ -9,4 +9,4 @@ edition = "2021"
 config = { version = "0.10.1", default-features = false, features = ["yaml"] }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
-serde-aux = "1.0.1"
+serde-aux = "1.1.0"

+ 2 - 2
shared-lib/flowy-client-ws/Cargo.toml

@@ -8,8 +8,8 @@ edition = "2021"
 [dependencies]
 lib-ws = { path = "../lib-ws" }
 lib-infra = { path = "../lib-infra" }
-futures-util = "0.3.15"
-tokio = {version = "1", features = ["sync"]}
+futures-util = "0.3.26"
+tokio = { version = "1.26", features = ["sync"]}
 parking_lot = "0.12.1"
 tracing = { version = "0.1", features = ["log"] }
 thiserror = "1.0"

+ 5 - 5
shared-lib/flowy-server-sync/Cargo.toml

@@ -11,13 +11,13 @@ ws-model = { path = "../ws-model" }
 document-model = { path = "../document-model" }
 folder-model = { path = "../folder-model" }
 flowy-sync = { path = "../flowy-sync" }
-bytes = "1.0"
-log = "0.4.14"
-tokio = { version = "1", features = ["full"] }
+bytes = "1.4"
+log = "0.4.17"
+tokio = { version = "1.26", features = ["full"] }
 serde = { version = "1.0", features = ["derive", "rc"] }
 lib-ot = { path = "../lib-ot" }
 lib-infra = { path = "../lib-infra" }
 dashmap = "5"
-futures = "0.3.15"
-async-stream = "0.3.2"
+futures = "0.3.26"
+async-stream = "0.3.4"
 tracing = { version = "0.1", features = ["log"] }

+ 3 - 6
shared-lib/flowy-server-sync/src/server_document/document_manager.rs

@@ -291,12 +291,9 @@ impl DocumentCommandRunner {
       .expect("DocumentCommandRunner's receiver should only take one time");
 
     let stream = stream! {
-        loop {
-            match receiver.recv().await {
-                Some(msg) => yield msg,
-                None => break,
-            }
-        }
+      while let Some(msg) = receiver.recv().await {
+        yield msg;
+      }
     };
     stream.for_each(|msg| self.handle_message(msg)).await;
   }

+ 3 - 6
shared-lib/flowy-server-sync/src/server_folder/folder_manager.rs

@@ -251,12 +251,9 @@ impl FolderCommandRunner {
       .expect("FolderCommandRunner's receiver should only take one time");
 
     let stream = stream! {
-        loop {
-            match receiver.recv().await {
-                Some(msg) => yield msg,
-                None => break,
-            }
-        }
+      while let Some(msg) = receiver.recv().await {
+        yield msg;
+      }
     };
     stream.for_each(|msg| self.handle_message(msg)).await;
   }

+ 1 - 1
shared-lib/flowy-sync/Cargo.toml

@@ -15,6 +15,6 @@ document-model = { path = "../document-model" }
 strum = "0.21"
 strum_macros = "0.21"
 parking_lot = "0.12.1"
-tokio = { version = "1", features = ["full"] }
+tokio = { version = "1.26", features = ["full"] }
 serde = { version = "1.0", features = ["derive", "rc"] }
 tracing = { version = "0.1", features = ["log"] }

+ 4 - 4
shared-lib/lib-infra/Cargo.toml

@@ -6,11 +6,11 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-chrono = "0.4.19"
-bytes = { version = "1.0" }
+chrono = "0.4.23"
+bytes = { version = "1.4" }
 pin-project = "1.0.12"
 futures-core = { version = "0.3" }
-tokio = { version = "1", features = ["time", "rt"] }
+tokio = { version = "1.26", features = ["time", "rt"] }
 rand = "0.8.5"
-async-trait = "0.1.59"
+async-trait = "0.1.64"
 md5 = "0.7.0"

+ 3 - 3
shared-lib/lib-ot/Cargo.toml

@@ -9,14 +9,14 @@ edition = "2018"
 serde = { version = "1.0", features = ["derive", "rc"] }
 thiserror = "1.0"
 serde_json = { version = "1.0" }
-indexmap = {version = "1.9.1", features = ["serde"]}
+indexmap = {version = "1.9.2", features = ["serde"]}
 log = "0.4"
 tracing = { version = "0.1", features = ["log"] }
 lazy_static = "1.4.0"
 strum = "0.21"
 strum_macros = "0.21"
-bytes = "1.0"
-indextree = "4.4.0"
+bytes = "1.4"
+indextree = "4.5.0"
 
 
 [features]

+ 9 - 9
shared-lib/lib-ws/Cargo.toml

@@ -12,21 +12,21 @@ serde_json = {version = "1.0"}
 lib-infra = { path = "../lib-infra" }
 
 tokio-tungstenite = "0.15"
-futures-util = "0.3.17"
-futures-channel = "0.3.17"
-tokio = {version = "1", features = ["full"]}
-futures = "0.3.17"
-bytes = "1.0"
+futures-util = "0.3.26"
+futures-channel = "0.3.26"
+tokio = { version = "1.26", features = ["full"]}
+futures = "0.3.26"
+bytes = "1.4"
 pin-project = "1.0"
 futures-core = { version = "0.3", default-features = false }
-url = "2.2.2"
+url = "2.3.1"
 log = "0.4"
 tracing = { version = "0.1", features = ["log"] }
-protobuf = {version = "2.18.0"}
+protobuf = {version = "2.28.0"}
 strum_macros = "0.21"
 parking_lot = "0.12.1"
 dashmap = "5"
 
 [dev-dependencies]
-tokio = {version = "1", features = ["full"]}
-env_logger = "0.8.2"
+tokio = { version = "1.26", features = ["full"]}
+env_logger = "0.8.4"

+ 1 - 1
shared-lib/revision-model/Cargo.toml

@@ -9,4 +9,4 @@ edition = "2021"
 serde = { version = "1.0" }
 serde_json = { version = "1.0" }
 md5 = "0.7.0"
-bytes = "1.0"
+bytes = "1.4"

+ 6 - 6
shared-lib/user-model/Cargo.toml

@@ -8,7 +8,7 @@ edition = "2021"
 [dependencies]
 serde = { version = "1.0" }
 validator = "0.16.0"
-unicode-segmentation = "1.8"
+unicode-segmentation = "1.10"
 fancy-regex = "0.11.0"
 lazy_static = "1.4.0"
 tracing = { version = "0.1", features = ["log"] }
@@ -19,9 +19,9 @@ serde_repr = "0.1"
 nanoid = "0.4.0"
 quickcheck = "1.0.3"
 quickcheck_macros = "0.9.1"
-fake = "2.4.3"
-claim = "0.4.0"
-futures = "0.3.15"
+fake = "2.5.0"
+#claim = "0.5.0"
+futures = "0.3.26"
 serial_test = "0.5.1"
-rand_core = "0.6.3"
-rand = "0.8.5"
+rand_core = "0.6.4"
+rand = "0.8.5"

+ 3 - 4
shared-lib/user-model/src/parser/user_email.rs

@@ -27,7 +27,6 @@ impl AsRef<str> for UserEmail {
 #[cfg(test)]
 mod tests {
   use super::*;
-  use claim::assert_err;
   use fake::{faker::internet::en::SafeEmail, Fake};
   use rand::prelude::StdRng;
   use rand_core::SeedableRng;
@@ -35,19 +34,19 @@ mod tests {
   #[test]
   fn empty_string_is_rejected() {
     let email = "".to_string();
-    assert_err!(UserEmail::parse(email));
+    assert!(UserEmail::parse(email).is_err());
   }
 
   #[test]
   fn email_missing_at_symbol_is_rejected() {
     let email = "helloworld.com".to_string();
-    assert_err!(UserEmail::parse(email));
+    assert!(UserEmail::parse(email).is_err());
   }
 
   #[test]
   fn email_missing_subject_is_rejected() {
     let email = "@domain.com".to_string();
-    assert_err!(UserEmail::parse(email));
+    assert!(UserEmail::parse(email).is_err());
   }
 
   #[derive(Debug, Clone)]

+ 6 - 7
shared-lib/user-model/src/parser/user_name.rs

@@ -42,43 +42,42 @@ impl AsRef<str> for UserName {
 #[cfg(test)]
 mod tests {
   use super::UserName;
-  use claim::{assert_err, assert_ok};
 
   #[test]
   fn a_256_grapheme_long_name_is_valid() {
     let name = "a̐".repeat(256);
-    assert_ok!(UserName::parse(name));
+    assert!(UserName::parse(name).is_ok());
   }
 
   #[test]
   fn a_name_longer_than_256_graphemes_is_rejected() {
     let name = "a".repeat(257);
-    assert_err!(UserName::parse(name));
+    assert!(UserName::parse(name).is_err());
   }
 
   #[test]
   fn whitespace_only_names_are_rejected() {
     let name = " ".to_string();
-    assert_err!(UserName::parse(name));
+    assert!(UserName::parse(name).is_err());
   }
 
   #[test]
   fn empty_string_is_rejected() {
     let name = "".to_string();
-    assert_err!(UserName::parse(name));
+    assert!(UserName::parse(name).is_err());
   }
 
   #[test]
   fn names_containing_an_invalid_character_are_rejected() {
     for name in &['/', '(', ')', '"', '<', '>', '\\', '{', '}'] {
       let name = name.to_string();
-      assert_err!(UserName::parse(name));
+      assert!(UserName::parse(name).is_err());
     }
   }
 
   #[test]
   fn a_valid_name_is_parsed_successfully() {
     let name = "nathan".to_string();
-    assert_ok!(UserName::parse(name));
+    assert!(UserName::parse(name).is_ok());
   }
 }

+ 1 - 1
shared-lib/ws-model/Cargo.toml

@@ -10,4 +10,4 @@ serde = { version = "1.0" }
 serde_json = { version = "1.0" }
 serde_repr = "0.1"
 revision-model = { path = "../revision-model"}
-bytes = "1.0"
+bytes = "1.4"