diff --git a/native/cpp/CommonCpp/NativeModules/CMakeLists.txt b/native/cpp/CommonCpp/NativeModules/CMakeLists.txt
--- a/native/cpp/CommonCpp/NativeModules/CMakeLists.txt
+++ b/native/cpp/CommonCpp/NativeModules/CMakeLists.txt
@@ -11,6 +11,7 @@
   "CommCoreModule.h"
   "CommUtilsModule.h"
   "CommConstants.h"
+  "NativeModuleUtils.h"
   "MessageStoreOperations.h"
   "ThreadStoreOperations.h"
   "ReportStoreOperations.h"
diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h
--- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h
+++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h
@@ -23,8 +23,6 @@
   const std::string publicCryptoAccountID = "publicCryptoAccountID";
   std::unique_ptr<crypto::CryptoModule> cryptoModule;
 
-  template <class T>
-  T runSyncOrThrowJSError(jsi::Runtime &rt, std::function<T()> task);
   virtual jsi::Value getDraft(jsi::Runtime &rt, jsi::String key) override;
   virtual jsi::Value
   updateDraft(jsi::Runtime &rt, jsi::String key, jsi::String text) override;
diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp
--- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp
+++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp
@@ -6,6 +6,7 @@
 #include "InternalModules/GlobalDBSingleton.h"
 #include "InternalModules/RustPromiseManager.h"
 #include "MessageStoreOperations.h"
+#include "NativeModuleUtils.h"
 #include "ReportStoreOperations.h"
 #include "TerminateApp.h"
 #include "ThreadStoreOperations.h"
@@ -20,33 +21,6 @@
 
 using namespace facebook::react;
 
-template <class T>
-T CommCoreModule::runSyncOrThrowJSError(
-    jsi::Runtime &rt,
-    std::function<T()> task) {
-  std::promise<T> promise;
-  GlobalDBSingleton::instance.scheduleOrRunCancellable([&promise, &task]() {
-    try {
-      if constexpr (std::is_void<T>::value) {
-        task();
-        promise.set_value();
-      } else {
-        promise.set_value(task());
-      }
-    } catch (const std::exception &e) {
-      promise.set_exception(std::make_exception_ptr(e));
-    }
-  });
-  // We cannot instantiate JSError on database thread, so
-  // on the main thread we re-throw C++ error, catch it and
-  // transform to informative JSError on the main thread
-  try {
-    return promise.get_future().get();
-  } catch (const std::exception &e) {
-    throw jsi::JSError(rt, e.what());
-  }
-}
-
 jsi::Value CommCoreModule::getDraft(jsi::Runtime &rt, jsi::String key) {
   std::string keyStr = key.utf8(rt);
   return createPromiseAsJSIValue(
@@ -401,7 +375,7 @@
 }
 
 jsi::Array CommCoreModule::getAllMessagesSync(jsi::Runtime &rt) {
-  auto messagesVector = this->runSyncOrThrowJSError<
+  auto messagesVector = NativeModuleUtils::runSyncOrThrowJSError<
       std::vector<std::pair<Message, std::vector<Media>>>>(rt, []() {
     return DatabaseManager::getQueryExecutor().getAllMessages();
   });
@@ -608,7 +582,7 @@
     throw jsi::JSError(rt, e.what());
   }
 
-  this->runSyncOrThrowJSError<void>(rt, [&messageStoreOps]() {
+  NativeModuleUtils::runSyncOrThrowJSError<void>(rt, [&messageStoreOps]() {
     try {
       DatabaseManager::getQueryExecutor().beginTransaction();
       for (const auto &operation : messageStoreOps) {
@@ -623,8 +597,10 @@
 }
 
 jsi::Array CommCoreModule::getAllThreadsSync(jsi::Runtime &rt) {
-  auto threadsVector = this->runSyncOrThrowJSError<std::vector<Thread>>(
-      rt, []() { return DatabaseManager::getQueryExecutor().getAllThreads(); });
+  auto threadsVector =
+      NativeModuleUtils::runSyncOrThrowJSError<std::vector<Thread>>(rt, []() {
+        return DatabaseManager::getQueryExecutor().getAllThreads();
+      });
 
   auto threadsVectorPtr =
       std::make_shared<std::vector<Thread>>(std::move(threadsVector));
@@ -804,7 +780,7 @@
     throw jsi::JSError(rt, e.what());
   }
 
-  this->runSyncOrThrowJSError<void>(rt, [&threadStoreOps]() {
+  NativeModuleUtils::runSyncOrThrowJSError<void>(rt, [&threadStoreOps]() {
     try {
       DatabaseManager::getQueryExecutor().beginTransaction();
       for (const auto &operation : threadStoreOps) {
@@ -905,7 +881,7 @@
     throw jsi::JSError(rt, e.what());
   }
 
-  this->runSyncOrThrowJSError<void>(rt, [&reportStoreOps]() {
+  NativeModuleUtils::runSyncOrThrowJSError<void>(rt, [&reportStoreOps]() {
     try {
       DatabaseManager::getQueryExecutor().beginTransaction();
       for (const auto &operation : reportStoreOps) {
diff --git a/native/cpp/CommonCpp/NativeModules/NativeModuleUtils.h b/native/cpp/CommonCpp/NativeModules/NativeModuleUtils.h
new file mode 100644
--- /dev/null
+++ b/native/cpp/CommonCpp/NativeModules/NativeModuleUtils.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "InternalModules/GlobalDBSingleton.h"
+
+#include <jsi/jsi.h>
+#include <future>
+
+namespace comm {
+
+namespace jsi = facebook::jsi;
+
+class NativeModuleUtils {
+public:
+  template <class T>
+  static T runSyncOrThrowJSError(jsi::Runtime &rt, std::function<T()> task) {
+    std::promise<T> promise;
+    GlobalDBSingleton::instance.scheduleOrRunCancellable([&promise, &task]() {
+      try {
+        if constexpr (std::is_void<T>::value) {
+          task();
+          promise.set_value();
+        } else {
+          promise.set_value(task());
+        }
+      } catch (const std::exception &e) {
+        promise.set_exception(std::make_exception_ptr(e));
+      }
+    });
+    // We cannot instantiate JSError on database thread, so
+    // on the main thread we re-throw C++ error, catch it and
+    // transform to informative JSError on the main thread
+    try {
+      return promise.get_future().get();
+    } catch (const std::exception &e) {
+      throw jsi::JSError(rt, e.what());
+    }
+  }
+};
+
+} // namespace comm
diff --git a/native/ios/Comm.xcodeproj/project.pbxproj b/native/ios/Comm.xcodeproj/project.pbxproj
--- a/native/ios/Comm.xcodeproj/project.pbxproj
+++ b/native/ios/Comm.xcodeproj/project.pbxproj
@@ -193,6 +193,7 @@
 		8E43C32B291E5B4A009378F5 /* TerminateApp.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = TerminateApp.mm; path = Comm/TerminateApp.mm; sourceTree = "<group>"; };
 		8E43C32E291E5B9D009378F5 /* TerminateApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TerminateApp.h; path = ../cpp/CommonCpp/Tools/TerminateApp.h; sourceTree = "<group>"; };
 		8E86A6D229537EBB000BBE7D /* DatabaseManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DatabaseManager.cpp; sourceTree = "<group>"; };
+		8EA59BD22A6E800100EB4F53 /* NativeModuleUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NativeModuleUtils.h; sourceTree = "<group>"; };
 		8ED8B5322A4DCCE300D3DA26 /* CommQueryExecutor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommQueryExecutor.h; sourceTree = "<group>"; };
 		8ED8B5332A4DCCE300D3DA26 /* CommQueryExecutor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CommQueryExecutor.cpp; sourceTree = "<group>"; };
 		8EE6E49F2A39CCAB00AE6BCD /* ReportStoreOperations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReportStoreOperations.h; sourceTree = "<group>"; };
@@ -386,6 +387,7 @@
 		71BE843A2636A944002849D2 /* NativeModules */ = {
 			isa = PBXGroup;
 			children = (
+				8EA59BD22A6E800100EB4F53 /* NativeModuleUtils.h */,
 				8EE6E4A02A39CCAB00AE6BCD /* DraftStoreOperations.h */,
 				8EE6E49F2A39CCAB00AE6BCD /* ReportStoreOperations.h */,
 				CB2688FF2A2DF56000EC7300 /* CommConstants.cpp */,