Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion android/src/main/java/com/prisma/PrismaModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,17 @@ public void createDir(File dir) throws IOException
public void install() {
ReactApplicationContext context = this.getReactApplicationContext();
long jsContextPointer = context.getJavaScriptContextHolder().get();
CallInvokerHolderImpl jsCallInvokerHolder = (CallInvokerHolderImpl)context.getCatalystInstance().getJSCallInvokerHolder();
if (jsContextPointer == 0) {
throw new RuntimeException("JSI runtime pointer is null. Make sure Hermes is enabled and the runtime is initialized before calling install().");
}
CallInvokerHolderImpl jsCallInvokerHolder;
if (context.getJSCallInvokerHolder() != null) {
jsCallInvokerHolder = (CallInvokerHolderImpl) context.getJSCallInvokerHolder();
} else if (context.getCatalystInstance() != null) {
jsCallInvokerHolder = (CallInvokerHolderImpl) context.getCatalystInstance().getJSCallInvokerHolder();
} else {
throw new RuntimeException("JSCallInvokerHolder is not available yet.");
}
Comment on lines +125 to +135
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Null-safety improvements are good, but the fallback path may still NPE.

The null checks for jsContextPointer and getJSCallInvokerHolder() are correct. However, on line 132, context.getCatalystInstance().getJSCallInvokerHolder() may also return null even when getCatalystInstance() is non-null, leading to an NPE during the cast.

🛠️ Proposed fix to add null check
     if (context.getJSCallInvokerHolder() != null) {
       jsCallInvokerHolder = (CallInvokerHolderImpl) context.getJSCallInvokerHolder();
     } else if (context.getCatalystInstance() != null) {
-      jsCallInvokerHolder = (CallInvokerHolderImpl) context.getCatalystInstance().getJSCallInvokerHolder();
+      var holder = context.getCatalystInstance().getJSCallInvokerHolder();
+      if (holder == null) {
+        throw new RuntimeException("JSCallInvokerHolder from CatalystInstance is null.");
+      }
+      jsCallInvokerHolder = (CallInvokerHolderImpl) holder;
     } else {
       throw new RuntimeException("JSCallInvokerHolder is not available yet.");
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (jsContextPointer == 0) {
throw new RuntimeException("JSI runtime pointer is null. Make sure Hermes is enabled and the runtime is initialized before calling install().");
}
CallInvokerHolderImpl jsCallInvokerHolder;
if (context.getJSCallInvokerHolder() != null) {
jsCallInvokerHolder = (CallInvokerHolderImpl) context.getJSCallInvokerHolder();
} else if (context.getCatalystInstance() != null) {
jsCallInvokerHolder = (CallInvokerHolderImpl) context.getCatalystInstance().getJSCallInvokerHolder();
} else {
throw new RuntimeException("JSCallInvokerHolder is not available yet.");
}
if (jsContextPointer == 0) {
throw new RuntimeException("JSI runtime pointer is null. Make sure Hermes is enabled and the runtime is initialized before calling install().");
}
CallInvokerHolderImpl jsCallInvokerHolder;
if (context.getJSCallInvokerHolder() != null) {
jsCallInvokerHolder = (CallInvokerHolderImpl) context.getJSCallInvokerHolder();
} else if (context.getCatalystInstance() != null) {
var holder = context.getCatalystInstance().getJSCallInvokerHolder();
if (holder == null) {
throw new RuntimeException("JSCallInvokerHolder from CatalystInstance is null.");
}
jsCallInvokerHolder = (CallInvokerHolderImpl) holder;
} else {
throw new RuntimeException("JSCallInvokerHolder is not available yet.");
}

String dbPath = context.getDatabasePath("defaultDatabase").getAbsolutePath().replace("defaultDatabase", "");
String migrationsPath;
try {
Expand Down
3 changes: 1 addition & 2 deletions copy-migrations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ echo "Copying prisma migration files..."
MIGRATIONS_TARGET=${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}

rm -rf "$MIGRATIONS_TARGET/migrations"
mkdir "$MIGRATIONS_TARGET/migrations"
cp -r ${SRCROOT}/../migrations ${MIGRATIONS_TARGET}
cp -r ${SRCROOT}/../prisma/migrations "${MIGRATIONS_TARGET}/migrations"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Quote the source path to prevent word splitting.

If SRCROOT contains spaces, this command will fail. Quote the variable expansion for robustness.

🛠️ Proposed fix
-cp -r ${SRCROOT}/../prisma/migrations "${MIGRATIONS_TARGET}/migrations"
+cp -r "${SRCROOT}/../prisma/migrations" "${MIGRATIONS_TARGET}/migrations"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cp -r ${SRCROOT}/../prisma/migrations "${MIGRATIONS_TARGET}/migrations"
cp -r "${SRCROOT}/../prisma/migrations" "${MIGRATIONS_TARGET}/migrations"
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 8-8: Double quote to prevent globbing and word splitting.

(SC2086)


echo "migration files copied ✅"
7 changes: 6 additions & 1 deletion cpp/QueryEngineHostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
#ifndef query_engine_host_object_h
#define query_engine_host_object_h

#include "query_engine.h"
#include <TargetConditionals.h>
#if TARGET_OS_SIMULATOR
#include "../engines/ios/QueryEngine.xcframework/ios-arm64_x86_64-simulator/Headers/query_engine.h"
#else
#include "../engines/ios/QueryEngine.xcframework/ios-arm64/Headers/query_engine.h"
#endif
Comment on lines +5 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing platform guard will break non-Apple builds.

<TargetConditionals.h> is an Apple-only header. This code compiles on Android where this header doesn't exist, causing a build failure. Additionally, cpp/react-native-prisma.cpp:5 still uses #include "query_engine.h" directly, creating inconsistency.

Wrap the Apple-specific conditional in a platform check:

🐛 Proposed fix to add platform guard
+#if defined(__APPLE__)
 `#include` <TargetConditionals.h>
 `#if` TARGET_OS_SIMULATOR
 `#include` "../engines/ios/QueryEngine.xcframework/ios-arm64_x86_64-simulator/Headers/query_engine.h"
 `#else`
 `#include` "../engines/ios/QueryEngine.xcframework/ios-arm64/Headers/query_engine.h"
 `#endif`
+#else
+#include "query_engine.h"
+#endif
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <TargetConditionals.h>
#if TARGET_OS_SIMULATOR
#include "../engines/ios/QueryEngine.xcframework/ios-arm64_x86_64-simulator/Headers/query_engine.h"
#else
#include "../engines/ios/QueryEngine.xcframework/ios-arm64/Headers/query_engine.h"
#endif
`#if` defined(__APPLE__)
`#include` <TargetConditionals.h>
`#if` TARGET_OS_SIMULATOR
`#include` "../engines/ios/QueryEngine.xcframework/ios-arm64_x86_64-simulator/Headers/query_engine.h"
`#else`
`#include` "../engines/ios/QueryEngine.xcframework/ios-arm64/Headers/query_engine.h"
`#endif`
`#else`
`#include` "query_engine.h"
`#endif`
🧰 Tools
🪛 Clang (14.0.6)

[error] 5-5: 'TargetConditionals.h' file not found

(clang-diagnostic-error)

#include <jsi/jsi.h>
#include <memory>
#include <string>
Expand Down
4 changes: 2 additions & 2 deletions cpp/react-native-prisma.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void install_cxx(jsi::Runtime &rt,
}

auto log_callback_fn = [&rt, js_log_callback](std::string msg) {
call_invoker->invokeAsync([&rt, msg, &js_log_callback] {
call_invoker->invokeAsync([&rt, msg, js_log_callback] {
js_log_callback->asObject(rt).asFunction(rt).call(
rt, jsi::String::createFromUtf8(rt, msg));
});
Expand Down Expand Up @@ -130,7 +130,7 @@ void install_cxx(jsi::Runtime &rt,
auto resolve = std::make_shared<jsi::Value>(rt, args[0]);
auto reject = std::make_shared<jsi::Value>(rt, args[1]);

auto task = [&rt, &queryEngineHostObject, body = std::move(body),
auto task = [&rt, queryEngineHostObject, body = std::move(body),
trace = std::move(trace), tx_id = std::move(tx_id), resolve,
reject]() {
const char *response;
Expand Down
115 changes: 91 additions & 24 deletions ios/Prisma.mm
Original file line number Diff line number Diff line change
@@ -1,52 +1,119 @@
#import "Prisma.h"
#import <jsi/jsi.h>
#import <React/RCTBridge.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <dispatch/dispatch.h>
#import <ReactCommon/RCTTurboModule.h>
#import <UIKit/UIKit.h>
#import <iostream>

#ifdef RCT_NEW_ARCH_ENABLED
#import "RNPrismaSpecJSI.h"
#endif

// Forward declare runtimeExecutor to silence selector warnings on older headers
@interface RCTBridge (RuntimeExecutorForwardDecl)
- (facebook::react::RuntimeExecutor)runtimeExecutor;
@end

static NSString *PrismaLibraryPath() {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, true);
return [paths objectAtIndex:0];
}

static NSString *PrismaMigrationsPath() {
auto bundleURL = NSBundle.mainBundle.bundleURL;
auto migrations_path_absolute = [NSString stringWithFormat:@"%@%@", bundleURL.absoluteString, @"migrations"];
return [migrations_path_absolute stringByReplacingOccurrencesOfString:@"file://" withString:@""];
}

static inline void InstallInRuntime(facebook::jsi::Runtime &runtime,
std::shared_ptr<facebook::react::CallInvoker> callInvoker) {
NSString *libraryPath = PrismaLibraryPath();
NSString *migrationsPath = PrismaMigrationsPath();
prisma::install_cxx(runtime, callInvoker, [libraryPath UTF8String], [migrationsPath UTF8String]);
}

static RCTBridge *ResolveBridge(id<RCTBridgeModule> module) {
RCTBridge *bridge = nil;
if ([module respondsToSelector:@selector(bridge)]) {
bridge = ((id<RCTBridgeModule>)module).bridge;
}
if (bridge == nil) {
bridge = [RCTBridge currentBridge];
}
return bridge;
}

@implementation Prisma

@synthesize bridge=_bridge;

RCT_EXPORT_MODULE()

// Old-arch sync install for classic bridge
#if !RCT_NEW_ARCH_ENABLED
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)
{
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)_bridge;
if (cxxBridge == nil) {
return @false;
#if DEBUG
std::cout << "▲ NSHomeDirectory:\n" << [NSHomeDirectory() UTF8String] << std::endl;
std::cout << "▲ Library Path:\n" << [PrismaLibraryPath() UTF8String] << std::endl;
std::cout << "▲ Migrations Path:\n" << [PrismaMigrationsPath() UTF8String] << std::endl;
#endif

BOOL ok = NO;
auto okPtr = &ok;
RCTBridge *bridge = ResolveBridge(self);

// Try new-arch path first if runtimeExecutor is available
if (bridge && [bridge respondsToSelector:@selector(runtimeExecutor)]) {
facebook::react::RuntimeExecutor executor = RCTRuntimeExecutorFromBridge(bridge);
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
executor([&](facebook::jsi::Runtime &runtime) {
InstallInRuntime(runtime, bridge.jsCallInvoker);
*okPtr = YES;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return @(ok);
}
Comment on lines +70 to 80
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential deadlock if InstallInRuntime throws.

If InstallInRuntime throws an exception inside the executor block, dispatch_semaphore_signal is never called, causing dispatch_semaphore_wait to block indefinitely.

🐛 Proposed fix using try-catch
     if (bridge && [bridge respondsToSelector:`@selector`(runtimeExecutor)]) {
         facebook::react::RuntimeExecutor executor = RCTRuntimeExecutorFromBridge(bridge);
         dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+        __block NSException *caughtException = nil;
         executor([&](facebook::jsi::Runtime &runtime) {
-          InstallInRuntime(runtime, bridge.jsCallInvoker);
-          *okPtr = YES;
+          `@try` {
+            InstallInRuntime(runtime, bridge.jsCallInvoker);
+            *okPtr = YES;
+          } `@catch` (NSException *e) {
+            caughtException = e;
+          }
           dispatch_semaphore_signal(sema);
         });
         dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
+        if (caughtException) {
+          `@throw` caughtException;
+        }
         return @(ok);
     }

Note: C++ exceptions from JSI may need different handling (e.g., std::exception_ptr). Consider using a C++ try-catch block instead if JSI throws C++ exceptions.


auto jsiRuntime = (facebook::jsi::Runtime *)cxxBridge.runtime;
if (jsiRuntime == nil) {

// Fallback to old-arch bridge access
RCTBridge *legacyBridge = _bridge ?: bridge;
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)legacyBridge;
if (cxxBridge == nil || cxxBridge.runtime == nil) {
NSLog(@"[Prisma] no runtime available to install cxx");
return @false;
}

auto jsiRuntime = (facebook::jsi::Runtime *)cxxBridge.runtime;
auto &runtime = *jsiRuntime;
auto callInvoker = _bridge.jsCallInvoker;

// get migrations folder
auto bundleURL = NSBundle.mainBundle.bundleURL;
auto migrations_path_absolute = [NSString stringWithFormat:@"%@%@", bundleURL.absoluteString, @"migrations"];
auto migrations_path = [migrations_path_absolute stringByReplacingOccurrencesOfString:@"file://" withString:@""];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, true);
NSString *libraryPath = [paths objectAtIndex:0];

#if DEBUG
std::cout << "▲ NSHomeDirectory:\n" << [NSHomeDirectory() UTF8String] << std::endl;
std::cout << "▲ Library Path:\n" << [libraryPath UTF8String] << std::endl;
std::cout << "▲ Migrations Path:\n" << [migrations_path UTF8String] << std::endl;
#endif
auto callInvoker = _bridge ? _bridge.jsCallInvoker : legacyBridge.jsCallInvoker;

prisma::install_cxx(runtime, callInvoker, [libraryPath UTF8String], [migrations_path UTF8String]);
return nil;
InstallInRuntime(runtime, callInvoker);
return @true;
}
#else
// Required by NativePrismaSpec protocol in new-arch; TurboModule path uses C++ spec below.
- (void)install {}
#endif

#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativePrismaSpecJSI>(params);
class PrismaCxxTurboModule final : public facebook::react::NativePrismaCxxSpec<PrismaCxxTurboModule> {
public:
explicit PrismaCxxTurboModule(std::shared_ptr<facebook::react::CallInvoker> jsInvoker)
: NativePrismaCxxSpec(std::move(jsInvoker)) {}

void install(facebook::jsi::Runtime &rt) {
InstallInRuntime(rt, this->jsInvoker_);
}
};

return std::make_shared<PrismaCxxTurboModule>(params.jsInvoker);
}
#endif

Expand Down