pomegranate-db 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +38 -0
- package/PomegranateDB.podspec +67 -0
- package/README.md +122 -0
- package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.d.ts +34 -0
- package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.js +155 -0
- package/dist/adapters/expo-sqlite/index.d.ts +2 -0
- package/dist/adapters/expo-sqlite/index.js +6 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.js +13 -0
- package/dist/adapters/loki/LokiAdapter.d.ts +100 -0
- package/dist/adapters/loki/LokiAdapter.js +144 -0
- package/dist/adapters/loki/index.d.ts +6 -0
- package/dist/adapters/loki/index.js +12 -0
- package/dist/adapters/loki/worker/LokiDispatcher.d.ts +21 -0
- package/dist/adapters/loki/worker/LokiDispatcher.js +63 -0
- package/dist/adapters/loki/worker/LokiExecutor.d.ts +96 -0
- package/dist/adapters/loki/worker/LokiExecutor.js +462 -0
- package/dist/adapters/loki/worker/SynchronousWorker.d.ts +22 -0
- package/dist/adapters/loki/worker/SynchronousWorker.js +76 -0
- package/dist/adapters/loki/worker/loki.worker.d.ts +14 -0
- package/dist/adapters/loki/worker/loki.worker.js +112 -0
- package/dist/adapters/loki/worker/types.d.ts +44 -0
- package/dist/adapters/loki/worker/types.js +11 -0
- package/dist/adapters/native-sqlite/NativeSQLiteDriver.d.ts +55 -0
- package/dist/adapters/native-sqlite/NativeSQLiteDriver.js +145 -0
- package/dist/adapters/native-sqlite/index.d.ts +2 -0
- package/dist/adapters/native-sqlite/index.js +6 -0
- package/dist/adapters/op-sqlite/OpSQLiteDriver.d.ts +49 -0
- package/dist/adapters/op-sqlite/OpSQLiteDriver.js +140 -0
- package/dist/adapters/op-sqlite/index.d.ts +2 -0
- package/dist/adapters/op-sqlite/index.js +6 -0
- package/dist/adapters/sqlite/SQLiteAdapter.d.ts +70 -0
- package/dist/adapters/sqlite/SQLiteAdapter.js +264 -0
- package/dist/adapters/sqlite/index.d.ts +2 -0
- package/dist/adapters/sqlite/index.js +6 -0
- package/dist/adapters/sqlite/sql.d.ts +35 -0
- package/dist/adapters/sqlite/sql.js +258 -0
- package/dist/adapters/types.d.ts +93 -0
- package/dist/adapters/types.js +9 -0
- package/dist/collection/Collection.d.ts +103 -0
- package/dist/collection/Collection.js +245 -0
- package/dist/collection/index.d.ts +2 -0
- package/dist/collection/index.js +6 -0
- package/dist/database/Database.d.ts +128 -0
- package/dist/database/Database.js +245 -0
- package/dist/database/index.d.ts +2 -0
- package/dist/database/index.js +6 -0
- package/dist/encryption/index.d.ts +62 -0
- package/dist/encryption/index.js +276 -0
- package/dist/encryption/nodeCrypto.d.ts +18 -0
- package/dist/encryption/nodeCrypto.js +25 -0
- package/dist/encryption/nodeCrypto.native.d.ts +13 -0
- package/dist/encryption/nodeCrypto.native.js +26 -0
- package/dist/expo.d.ts +12 -0
- package/dist/expo.js +32 -0
- package/dist/hooks/index.d.ts +115 -0
- package/dist/hooks/index.js +285 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +57 -0
- package/dist/model/Model.d.ts +92 -0
- package/dist/model/Model.js +251 -0
- package/dist/model/index.d.ts +2 -0
- package/dist/model/index.js +7 -0
- package/dist/observable/Subject.d.ts +60 -0
- package/dist/observable/Subject.js +132 -0
- package/dist/observable/index.d.ts +2 -0
- package/dist/observable/index.js +10 -0
- package/dist/query/QueryBuilder.d.ts +51 -0
- package/dist/query/QueryBuilder.js +165 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +7 -0
- package/dist/query/types.d.ts +60 -0
- package/dist/query/types.js +9 -0
- package/dist/schema/builder.d.ts +68 -0
- package/dist/schema/builder.js +168 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +7 -0
- package/dist/schema/types.d.ts +108 -0
- package/dist/schema/types.js +9 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +6 -0
- package/dist/sync/sync.d.ts +15 -0
- package/dist/sync/sync.js +182 -0
- package/dist/sync/types.d.ts +41 -0
- package/dist/sync/types.js +6 -0
- package/dist/utils/index.d.ts +45 -0
- package/dist/utils/index.js +99 -0
- package/expo-plugin/index.d.ts +68 -0
- package/expo-plugin/index.js +83 -0
- package/native/android-jsi/build.gradle +45 -0
- package/native/android-jsi/src/main/AndroidManifest.xml +2 -0
- package/native/android-jsi/src/main/cpp/CMakeLists.txt +73 -0
- package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.cpp +107 -0
- package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.h +16 -0
- package/native/android-jsi/src/main/cpp/JSIInstaller.cpp +27 -0
- package/native/android-jsi/src/main/java/com/pomegranate/jsi/JSIInstaller.kt +43 -0
- package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIModule.kt +39 -0
- package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIPackage.kt +17 -0
- package/native/ios/DatabasePlatformIOS.mm +83 -0
- package/native/ios/PomegranateJSI.h +15 -0
- package/native/ios/PomegranateJSI.mm +59 -0
- package/native/shared/Database.cpp +283 -0
- package/native/shared/Database.h +84 -0
- package/native/shared/Sqlite.cpp +61 -0
- package/native/shared/Sqlite.h +67 -0
- package/native/shared/sqlite3/sqlite3.c +260493 -0
- package/native/shared/sqlite3/sqlite3.h +13583 -0
- package/package.json +127 -0
- package/react-native.config.js +28 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package com.pomegranate.jsi
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.JavaScriptContextHolder
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
7
|
+
import com.facebook.react.bridge.ReactMethod
|
|
8
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* React Native module that installs the PomegranateDB JSI binding.
|
|
12
|
+
*
|
|
13
|
+
* JS should call PomegranateJSIBridge.install() early in the app lifecycle
|
|
14
|
+
* (before using any native SQLite adapter). This is a synchronous blocking
|
|
15
|
+
* method that registers the global nativePomegranateCreateAdapter function.
|
|
16
|
+
*/
|
|
17
|
+
@ReactModule(name = PomegranateJSIModule.NAME)
|
|
18
|
+
class PomegranateJSIModule(
|
|
19
|
+
private val reactContext: ReactApplicationContext,
|
|
20
|
+
) : ReactContextBaseJavaModule(reactContext) {
|
|
21
|
+
override fun getName(): String = NAME
|
|
22
|
+
|
|
23
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
24
|
+
fun install(): Boolean =
|
|
25
|
+
try {
|
|
26
|
+
val jsContext: JavaScriptContextHolder =
|
|
27
|
+
reactApplicationContext.javaScriptContextHolder!!
|
|
28
|
+
JSIInstaller.install(reactApplicationContext, jsContext.get())
|
|
29
|
+
Log.i(NAME, "Successfully installed PomegranateDB JSI Bindings!")
|
|
30
|
+
true
|
|
31
|
+
} catch (exception: Exception) {
|
|
32
|
+
Log.e(NAME, "Failed to install PomegranateDB JSI Bindings!", exception)
|
|
33
|
+
false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
companion object {
|
|
37
|
+
const val NAME = "PomegranateJSIBridge"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.pomegranate.jsi
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* React Native package that provides the PomegranateDB JSI module.
|
|
10
|
+
* Add this to your MainApplication's getPackages() list.
|
|
11
|
+
*/
|
|
12
|
+
class PomegranateJSIPackage : ReactPackage {
|
|
13
|
+
override fun createNativeModules(reactAppContext: ReactApplicationContext): List<NativeModule> =
|
|
14
|
+
listOf(PomegranateJSIModule(reactAppContext))
|
|
15
|
+
|
|
16
|
+
override fun createViewManagers(reactAppContext: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
|
|
17
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — iOS platform implementation.
|
|
3
|
+
*
|
|
4
|
+
* Provides platform::resolveDatabasePath via NSDocumentDirectory,
|
|
5
|
+
* and platform::consoleLog/consoleError via NSLog.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "Database.h"
|
|
9
|
+
#import <Foundation/Foundation.h>
|
|
10
|
+
#include <mutex>
|
|
11
|
+
#include <sqlite3.h>
|
|
12
|
+
|
|
13
|
+
#define LOG_TAG "pomegranate.jsi"
|
|
14
|
+
|
|
15
|
+
namespace pomegranate {
|
|
16
|
+
namespace platform {
|
|
17
|
+
|
|
18
|
+
// ─── Logging ─────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
void consoleLog(const std::string &message) {
|
|
21
|
+
NSLog(@"[%s] %s", LOG_TAG, message.c_str());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
void consoleError(const std::string &message) {
|
|
25
|
+
NSLog(@"[%s] ERROR: %s", LOG_TAG, message.c_str());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── SQLite initialization ───────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
static void sqliteLogCallback(void *, int err, const char *message) {
|
|
31
|
+
int errType = err & 255;
|
|
32
|
+
if (errType == SQLITE_WARNING) {
|
|
33
|
+
NSLog(@"[%s] sqlite warning (%d): %s", LOG_TAG, err, message);
|
|
34
|
+
} else if (errType == 0 || errType == SQLITE_CONSTRAINT ||
|
|
35
|
+
errType == SQLITE_SCHEMA || errType == SQLITE_NOTICE) {
|
|
36
|
+
// verbose — skip
|
|
37
|
+
} else {
|
|
38
|
+
NSLog(@"[%s] sqlite error (%d): %s", LOG_TAG, err, message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static std::once_flag sqliteInitFlag;
|
|
43
|
+
|
|
44
|
+
static void initializeSqlite() {
|
|
45
|
+
std::call_once(sqliteInitFlag, []() {
|
|
46
|
+
sqlite3_config(SQLITE_CONFIG_LOG, &sqliteLogCallback, nullptr);
|
|
47
|
+
sqlite3_soft_heap_limit(8 * 1024 * 1024);
|
|
48
|
+
sqlite3_initialize();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Path resolution ─────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
std::string resolveDatabasePath(const std::string &dbName) {
|
|
55
|
+
initializeSqlite();
|
|
56
|
+
|
|
57
|
+
NSError *err = nil;
|
|
58
|
+
NSURL *documentsUrl =
|
|
59
|
+
[NSFileManager.defaultManager URLForDirectory:NSDocumentDirectory
|
|
60
|
+
inDomain:NSUserDomainMask
|
|
61
|
+
appropriateForURL:nil
|
|
62
|
+
create:YES
|
|
63
|
+
error:&err];
|
|
64
|
+
if (err || !documentsUrl) {
|
|
65
|
+
NSString *desc = err ? err.localizedDescription : @"unknown";
|
|
66
|
+
throw std::runtime_error(
|
|
67
|
+
"PomegranateDB: failed to resolve documents directory — " +
|
|
68
|
+
std::string([desc cStringUsingEncoding:NSUTF8StringEncoding]));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
NSString *filename = [NSString stringWithFormat:@"%s.db", dbName.c_str()];
|
|
72
|
+
NSURL *dbUrl = [documentsUrl URLByAppendingPathComponent:filename];
|
|
73
|
+
const char *cPath = dbUrl.path.UTF8String;
|
|
74
|
+
|
|
75
|
+
if (!cPath) {
|
|
76
|
+
throw std::runtime_error("PomegranateDB: failed to construct db path for " + dbName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return std::string(cPath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
} // namespace platform
|
|
83
|
+
} // namespace pomegranate
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — iOS JSI Bridge.
|
|
3
|
+
*
|
|
4
|
+
* React Native bridge module that installs the PomegranateDB JSI binding.
|
|
5
|
+
* JS calls PomegranateJSI.install() early in the app lifecycle (before
|
|
6
|
+
* using any NativeSQLiteAdapter) to register the global
|
|
7
|
+
* nativePomegranateCreateAdapter function into the JS runtime.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#pragma once
|
|
11
|
+
|
|
12
|
+
#import <React/RCTBridgeModule.h>
|
|
13
|
+
|
|
14
|
+
@interface PomegranateJSI : NSObject <RCTBridgeModule>
|
|
15
|
+
@end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — iOS JSI Bridge implementation.
|
|
3
|
+
*
|
|
4
|
+
* Conforms to RCTBridgeModule and provides a synchronous install() method
|
|
5
|
+
* that registers nativePomegranateCreateAdapter into the JS runtime via JSI.
|
|
6
|
+
*
|
|
7
|
+
* Usage (JS side, early in app lifecycle):
|
|
8
|
+
* import { NativeModules } from 'react-native';
|
|
9
|
+
* NativeModules.PomegranateJSIBridge.install();
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
#import "PomegranateJSI.h"
|
|
13
|
+
#import <React/RCTBridge+Private.h>
|
|
14
|
+
#import <React/RCTLog.h>
|
|
15
|
+
#import <jsi/jsi.h>
|
|
16
|
+
|
|
17
|
+
// Our C++ database bridge
|
|
18
|
+
#import "Database.h"
|
|
19
|
+
|
|
20
|
+
@implementation PomegranateJSI
|
|
21
|
+
|
|
22
|
+
@synthesize bridge = _bridge;
|
|
23
|
+
|
|
24
|
+
// ─── RCTBridgeModule boilerplate ──────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
RCT_EXPORT_MODULE(PomegranateJSIBridge)
|
|
27
|
+
|
|
28
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
29
|
+
return NO;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── install() ────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Synchronously installs the JSI global into the current JS runtime.
|
|
36
|
+
* Must be called before using NativeSQLiteAdapter.
|
|
37
|
+
* Returns @YES on success, @NO if the bridge doesn't expose a runtime.
|
|
38
|
+
*/
|
|
39
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
|
|
40
|
+
@try {
|
|
41
|
+
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)_bridge;
|
|
42
|
+
if (!cxxBridge || !cxxBridge.runtime) {
|
|
43
|
+
RCTLogError(@"[PomegranateDB] Bridge or runtime is nil — cannot install JSI binding. "
|
|
44
|
+
@"Are you using the JSC / Hermes runtime?");
|
|
45
|
+
return @(NO);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
auto *runtime = (facebook::jsi::Runtime *)cxxBridge.runtime;
|
|
49
|
+
pomegranate::Database::install(*runtime);
|
|
50
|
+
|
|
51
|
+
RCTLogInfo(@"[PomegranateDB] Successfully installed JSI bindings!");
|
|
52
|
+
return @(YES);
|
|
53
|
+
} @catch (NSException *exception) {
|
|
54
|
+
RCTLogError(@"[PomegranateDB] Failed to install JSI bindings: %@", exception.reason);
|
|
55
|
+
return @(NO);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@end
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — JSI Database Bridge implementation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "Database.h"
|
|
6
|
+
#include <sstream>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
namespace pomegranate {
|
|
10
|
+
|
|
11
|
+
using namespace facebook;
|
|
12
|
+
|
|
13
|
+
// ─── Database lifecycle ──────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
Database::Database(jsi::Runtime &rt, const std::string &path) {
|
|
16
|
+
db_ = std::make_unique<SqliteDb>(path);
|
|
17
|
+
|
|
18
|
+
// Pragmas for performance
|
|
19
|
+
db_->execute("PRAGMA journal_mode = WAL");
|
|
20
|
+
db_->execute("PRAGMA synchronous = NORMAL");
|
|
21
|
+
db_->execute("PRAGMA busy_timeout = 5000");
|
|
22
|
+
db_->execute("PRAGMA temp_store = MEMORY");
|
|
23
|
+
db_->execute("PRAGMA cache_size = -8000"); // 8 MB
|
|
24
|
+
|
|
25
|
+
platform::consoleLog("PomegranateDB: opened " + path);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Database::~Database() {
|
|
29
|
+
close();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void Database::close() {
|
|
33
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
34
|
+
clearStmtCache();
|
|
35
|
+
db_.reset();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Statement cache ─────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
sqlite3_stmt *Database::cachedPrepare(const std::string &sql) {
|
|
41
|
+
auto it = stmtCache_.find(sql);
|
|
42
|
+
if (it != stmtCache_.end()) {
|
|
43
|
+
return it->second;
|
|
44
|
+
}
|
|
45
|
+
sqlite3_stmt *stmt = db_->prepare(sql);
|
|
46
|
+
stmtCache_[sql] = stmt;
|
|
47
|
+
return stmt;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void Database::clearStmtCache() {
|
|
51
|
+
for (auto &pair : stmtCache_) {
|
|
52
|
+
sqlite3_finalize(pair.second);
|
|
53
|
+
}
|
|
54
|
+
stmtCache_.clear();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Argument binding ────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
void Database::bindArgs(sqlite3_stmt *stmt, const jsi::Array &args, jsi::Runtime &rt) {
|
|
60
|
+
size_t count = args.size(rt);
|
|
61
|
+
for (size_t i = 0; i < count; i++) {
|
|
62
|
+
jsi::Value val = args.getValueAtIndex(rt, i);
|
|
63
|
+
int idx = static_cast<int>(i) + 1; // SQLite 1-indexed
|
|
64
|
+
|
|
65
|
+
if (val.isNull() || val.isUndefined()) {
|
|
66
|
+
sqlite3_bind_null(stmt, idx);
|
|
67
|
+
} else if (val.isBool()) {
|
|
68
|
+
sqlite3_bind_int(stmt, idx, val.getBool() ? 1 : 0);
|
|
69
|
+
} else if (val.isNumber()) {
|
|
70
|
+
double num = val.getNumber();
|
|
71
|
+
// If it looks like an integer, bind as int for efficiency
|
|
72
|
+
if (num == static_cast<double>(static_cast<int64_t>(num)) && num >= -9007199254740992.0 &&
|
|
73
|
+
num <= 9007199254740992.0) {
|
|
74
|
+
sqlite3_bind_int64(stmt, idx, static_cast<int64_t>(num));
|
|
75
|
+
} else {
|
|
76
|
+
sqlite3_bind_double(stmt, idx, num);
|
|
77
|
+
}
|
|
78
|
+
} else if (val.isString()) {
|
|
79
|
+
std::string str = val.getString(rt).utf8(rt);
|
|
80
|
+
sqlite3_bind_text(stmt, idx, str.c_str(), static_cast<int>(str.size()), SQLITE_TRANSIENT);
|
|
81
|
+
} else {
|
|
82
|
+
throw jsi::JSError(rt, "PomegranateDB: unsupported binding type at index " + std::to_string(i));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── Row reading ─────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
jsi::Object Database::rowToObject(sqlite3_stmt *stmt, jsi::Runtime &rt) {
|
|
90
|
+
jsi::Object obj(rt);
|
|
91
|
+
int columnCount = sqlite3_column_count(stmt);
|
|
92
|
+
|
|
93
|
+
for (int i = 0; i < columnCount; i++) {
|
|
94
|
+
const char *name = sqlite3_column_name(stmt, i);
|
|
95
|
+
int type = sqlite3_column_type(stmt, i);
|
|
96
|
+
|
|
97
|
+
switch (type) {
|
|
98
|
+
case SQLITE_NULL:
|
|
99
|
+
obj.setProperty(rt, name, jsi::Value::null());
|
|
100
|
+
break;
|
|
101
|
+
case SQLITE_INTEGER:
|
|
102
|
+
obj.setProperty(rt, name, jsi::Value(static_cast<double>(sqlite3_column_int64(stmt, i))));
|
|
103
|
+
break;
|
|
104
|
+
case SQLITE_FLOAT:
|
|
105
|
+
obj.setProperty(rt, name, jsi::Value(sqlite3_column_double(stmt, i)));
|
|
106
|
+
break;
|
|
107
|
+
case SQLITE_TEXT: {
|
|
108
|
+
const char *text = reinterpret_cast<const char *>(sqlite3_column_text(stmt, i));
|
|
109
|
+
obj.setProperty(rt, name, jsi::String::createFromUtf8(rt, text ? text : ""));
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case SQLITE_BLOB: {
|
|
113
|
+
// Store blobs as base64 strings? For simplicity, skip for now
|
|
114
|
+
const char *text = reinterpret_cast<const char *>(sqlite3_column_text(stmt, i));
|
|
115
|
+
obj.setProperty(rt, name, jsi::String::createFromUtf8(rt, text ? text : ""));
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return obj;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─── Database operations ─────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
void Database::execute(const std::string &sql, const jsi::Array &args, jsi::Runtime &rt) {
|
|
126
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
127
|
+
if (!db_) {
|
|
128
|
+
throw jsi::JSError(rt, "PomegranateDB: database is closed");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
sqlite3_stmt *stmt = cachedPrepare(sql);
|
|
132
|
+
SqliteStatement guard(stmt);
|
|
133
|
+
bindArgs(stmt, args, rt);
|
|
134
|
+
|
|
135
|
+
int result = sqlite3_step(stmt);
|
|
136
|
+
if (result != SQLITE_DONE && result != SQLITE_ROW) {
|
|
137
|
+
throw jsi::JSError(rt, "PomegranateDB: execute failed: " + db_->lastErrorMessage());
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
jsi::Array Database::query(const std::string &sql, const jsi::Array &args, jsi::Runtime &rt) {
|
|
142
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
143
|
+
if (!db_) {
|
|
144
|
+
throw jsi::JSError(rt, "PomegranateDB: database is closed");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
sqlite3_stmt *stmt = cachedPrepare(sql);
|
|
148
|
+
SqliteStatement guard(stmt);
|
|
149
|
+
bindArgs(stmt, args, rt);
|
|
150
|
+
|
|
151
|
+
std::vector<jsi::Object> rows;
|
|
152
|
+
while (true) {
|
|
153
|
+
int result = sqlite3_step(stmt);
|
|
154
|
+
if (result == SQLITE_ROW) {
|
|
155
|
+
rows.push_back(rowToObject(stmt, rt));
|
|
156
|
+
} else if (result == SQLITE_DONE) {
|
|
157
|
+
break;
|
|
158
|
+
} else {
|
|
159
|
+
throw jsi::JSError(rt, "PomegranateDB: query failed: " + db_->lastErrorMessage());
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
jsi::Array arr(rt, rows.size());
|
|
164
|
+
for (size_t i = 0; i < rows.size(); i++) {
|
|
165
|
+
arr.setValueAtIndex(rt, i, std::move(rows[i]));
|
|
166
|
+
}
|
|
167
|
+
return arr;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
int Database::executeBatch(const jsi::Array &commands, jsi::Runtime &rt) {
|
|
171
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
172
|
+
if (!db_) {
|
|
173
|
+
throw jsi::JSError(rt, "PomegranateDB: database is closed");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
int totalChanges = 0;
|
|
177
|
+
size_t count = commands.size(rt);
|
|
178
|
+
|
|
179
|
+
db_->execute("BEGIN TRANSACTION");
|
|
180
|
+
try {
|
|
181
|
+
for (size_t i = 0; i < count; i++) {
|
|
182
|
+
jsi::Object cmd = commands.getValueAtIndex(rt, i).getObject(rt);
|
|
183
|
+
std::string sql = cmd.getProperty(rt, "sql").getString(rt).utf8(rt);
|
|
184
|
+
jsi::Array args = cmd.getProperty(rt, "args").getObject(rt).getArray(rt);
|
|
185
|
+
|
|
186
|
+
sqlite3_stmt *stmt = cachedPrepare(sql);
|
|
187
|
+
SqliteStatement guard(stmt);
|
|
188
|
+
bindArgs(stmt, args, rt);
|
|
189
|
+
|
|
190
|
+
int result = sqlite3_step(stmt);
|
|
191
|
+
if (result != SQLITE_DONE && result != SQLITE_ROW) {
|
|
192
|
+
throw std::runtime_error(db_->lastErrorMessage());
|
|
193
|
+
}
|
|
194
|
+
totalChanges += sqlite3_changes(db_->getHandle());
|
|
195
|
+
}
|
|
196
|
+
db_->execute("COMMIT");
|
|
197
|
+
} catch (...) {
|
|
198
|
+
db_->execute("ROLLBACK");
|
|
199
|
+
throw;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return totalChanges;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ─── JSI Installation ────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
void Database::install(jsi::Runtime &rt) {
|
|
208
|
+
auto createAdapter = jsi::Function::createFromHostFunction(
|
|
209
|
+
rt, jsi::PropNameID::forAscii(rt, "nativePomegranateCreateAdapter"),
|
|
210
|
+
1, // dbName
|
|
211
|
+
[](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
|
|
212
|
+
if (count < 1 || !args[0].isString()) {
|
|
213
|
+
throw jsi::JSError(rt, "nativePomegranateCreateAdapter: expected database name string");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
std::string dbName = args[0].getString(rt).utf8(rt);
|
|
217
|
+
std::string dbPath = platform::resolveDatabasePath(dbName);
|
|
218
|
+
|
|
219
|
+
auto database = std::make_shared<Database>(rt, dbPath);
|
|
220
|
+
|
|
221
|
+
jsi::Object adapter(rt);
|
|
222
|
+
|
|
223
|
+
// ─── execute(sql, args) ──────────────────────────────
|
|
224
|
+
adapter.setProperty(
|
|
225
|
+
rt, "execute",
|
|
226
|
+
jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "execute"), 2,
|
|
227
|
+
[database](jsi::Runtime &rt, const jsi::Value &,
|
|
228
|
+
const jsi::Value *args, size_t count) -> jsi::Value {
|
|
229
|
+
if (count < 2) {
|
|
230
|
+
throw jsi::JSError(rt, "execute: expected (sql, args)");
|
|
231
|
+
}
|
|
232
|
+
std::string sql = args[0].getString(rt).utf8(rt);
|
|
233
|
+
jsi::Array bindArgs = args[1].getObject(rt).getArray(rt);
|
|
234
|
+
database->execute(sql, bindArgs, rt);
|
|
235
|
+
return jsi::Value::undefined();
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
// ─── query(sql, args) → Array<Object> ───────────────
|
|
239
|
+
adapter.setProperty(
|
|
240
|
+
rt, "query",
|
|
241
|
+
jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "query"), 2,
|
|
242
|
+
[database](jsi::Runtime &rt, const jsi::Value &,
|
|
243
|
+
const jsi::Value *args, size_t count) -> jsi::Value {
|
|
244
|
+
if (count < 2) {
|
|
245
|
+
throw jsi::JSError(rt, "query: expected (sql, args)");
|
|
246
|
+
}
|
|
247
|
+
std::string sql = args[0].getString(rt).utf8(rt);
|
|
248
|
+
jsi::Array bindArgs = args[1].getObject(rt).getArray(rt);
|
|
249
|
+
return database->query(sql, bindArgs, rt);
|
|
250
|
+
}));
|
|
251
|
+
|
|
252
|
+
// ─── executeBatch(commands) → number ─────────────────
|
|
253
|
+
adapter.setProperty(rt, "executeBatch",
|
|
254
|
+
jsi::Function::createFromHostFunction(
|
|
255
|
+
rt, jsi::PropNameID::forAscii(rt, "executeBatch"), 1,
|
|
256
|
+
[database](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
257
|
+
size_t count) -> jsi::Value {
|
|
258
|
+
if (count < 1) {
|
|
259
|
+
throw jsi::JSError(rt, "executeBatch: expected (commands)");
|
|
260
|
+
}
|
|
261
|
+
jsi::Array commands = args[0].getObject(rt).getArray(rt);
|
|
262
|
+
int changes = database->executeBatch(commands, rt);
|
|
263
|
+
return jsi::Value(changes);
|
|
264
|
+
}));
|
|
265
|
+
|
|
266
|
+
// ─── close() ────────────────────────────────────────
|
|
267
|
+
adapter.setProperty(
|
|
268
|
+
rt, "close",
|
|
269
|
+
jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "close"), 0,
|
|
270
|
+
[database](jsi::Runtime &rt, const jsi::Value &,
|
|
271
|
+
const jsi::Value *args, size_t count) -> jsi::Value {
|
|
272
|
+
database->close();
|
|
273
|
+
return jsi::Value::undefined();
|
|
274
|
+
}));
|
|
275
|
+
|
|
276
|
+
return adapter;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
rt.global().setProperty(rt, "nativePomegranateCreateAdapter", std::move(createAdapter));
|
|
280
|
+
platform::consoleLog("PomegranateDB: JSI bridge installed");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
} // namespace pomegranate
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — JSI Database Bridge.
|
|
3
|
+
*
|
|
4
|
+
* Exposes a `nativePomegranateCreateAdapter` global function to JS.
|
|
5
|
+
* When called, creates a C++ SQLite database and returns a JSI object
|
|
6
|
+
* with methods: open, execute, query, executeBatch, close.
|
|
7
|
+
*
|
|
8
|
+
* This is the core performance layer — all calls are synchronous JSI,
|
|
9
|
+
* no bridge serialization, no async queue.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
#pragma once
|
|
13
|
+
|
|
14
|
+
#include <jsi/jsi.h>
|
|
15
|
+
#include <memory>
|
|
16
|
+
#include <mutex>
|
|
17
|
+
#include <string>
|
|
18
|
+
#include <unordered_map>
|
|
19
|
+
#include "Sqlite.h"
|
|
20
|
+
|
|
21
|
+
namespace pomegranate {
|
|
22
|
+
|
|
23
|
+
using namespace facebook;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Platform-specific functions that must be implemented per-platform
|
|
27
|
+
* (Android, iOS, etc.)
|
|
28
|
+
*/
|
|
29
|
+
namespace platform {
|
|
30
|
+
/** Resolve a database name to an absolute path on disk. */
|
|
31
|
+
std::string resolveDatabasePath(const std::string &dbName);
|
|
32
|
+
|
|
33
|
+
/** Log to the platform console. */
|
|
34
|
+
void consoleLog(const std::string &message);
|
|
35
|
+
void consoleError(const std::string &message);
|
|
36
|
+
} // namespace platform
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The JSI database bridge.
|
|
40
|
+
*
|
|
41
|
+
* Calling Database::install(runtime) registers the global factory function.
|
|
42
|
+
* JS calls it to get adapter objects with synchronous database methods.
|
|
43
|
+
*/
|
|
44
|
+
class Database {
|
|
45
|
+
public:
|
|
46
|
+
Database(jsi::Runtime &rt, const std::string &path);
|
|
47
|
+
~Database();
|
|
48
|
+
|
|
49
|
+
/** Install the global `nativePomegranateCreateAdapter` function. */
|
|
50
|
+
static void install(jsi::Runtime &rt);
|
|
51
|
+
|
|
52
|
+
// ─── Database operations ───────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/** Execute a SQL statement with no return value. */
|
|
55
|
+
void execute(const std::string &sql, const jsi::Array &args, jsi::Runtime &rt);
|
|
56
|
+
|
|
57
|
+
/** Execute a query and return rows as an array of objects. */
|
|
58
|
+
jsi::Array query(const std::string &sql, const jsi::Array &args, jsi::Runtime &rt);
|
|
59
|
+
|
|
60
|
+
/** Execute multiple statements in a transaction. Returns rows affected. */
|
|
61
|
+
int executeBatch(const jsi::Array &commands, jsi::Runtime &rt);
|
|
62
|
+
|
|
63
|
+
/** Close the database. */
|
|
64
|
+
void close();
|
|
65
|
+
|
|
66
|
+
private:
|
|
67
|
+
std::unique_ptr<SqliteDb> db_;
|
|
68
|
+
std::mutex mutex_;
|
|
69
|
+
std::unordered_map<std::string, sqlite3_stmt *> stmtCache_;
|
|
70
|
+
|
|
71
|
+
/** Get or create a cached prepared statement. */
|
|
72
|
+
sqlite3_stmt *cachedPrepare(const std::string &sql);
|
|
73
|
+
|
|
74
|
+
/** Bind JSI arguments to a prepared statement. */
|
|
75
|
+
void bindArgs(sqlite3_stmt *stmt, const jsi::Array &args, jsi::Runtime &rt);
|
|
76
|
+
|
|
77
|
+
/** Read a result row into a JSI object. */
|
|
78
|
+
jsi::Object rowToObject(sqlite3_stmt *stmt, jsi::Runtime &rt);
|
|
79
|
+
|
|
80
|
+
/** Finalize all cached statements. */
|
|
81
|
+
void clearStmtCache();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
} // namespace pomegranate
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PomegranateDB — Native SQLite C++ implementation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "Sqlite.h"
|
|
6
|
+
#include <stdexcept>
|
|
7
|
+
|
|
8
|
+
namespace pomegranate {
|
|
9
|
+
|
|
10
|
+
SqliteDb::SqliteDb(const std::string &path) {
|
|
11
|
+
int result = sqlite3_open(path.c_str(), &db_);
|
|
12
|
+
if (result != SQLITE_OK) {
|
|
13
|
+
std::string error = db_ ? sqlite3_errmsg(db_) : "Unknown error";
|
|
14
|
+
if (db_) {
|
|
15
|
+
sqlite3_close(db_);
|
|
16
|
+
db_ = nullptr;
|
|
17
|
+
}
|
|
18
|
+
throw std::runtime_error("Failed to open database: " + error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
SqliteDb::~SqliteDb() {
|
|
23
|
+
if (db_) {
|
|
24
|
+
// Finalize any remaining statements
|
|
25
|
+
sqlite3_stmt *stmt = nullptr;
|
|
26
|
+
while ((stmt = sqlite3_next_stmt(db_, nullptr)) != nullptr) {
|
|
27
|
+
sqlite3_finalize(stmt);
|
|
28
|
+
}
|
|
29
|
+
sqlite3_close(db_);
|
|
30
|
+
db_ = nullptr;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void SqliteDb::execute(const std::string &sql) {
|
|
35
|
+
char *errMsg = nullptr;
|
|
36
|
+
int result = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &errMsg);
|
|
37
|
+
if (result != SQLITE_OK) {
|
|
38
|
+
std::string error = errMsg ? errMsg : "Unknown error";
|
|
39
|
+
sqlite3_free(errMsg);
|
|
40
|
+
throw std::runtime_error("SQL execution failed: " + error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
sqlite3_stmt *SqliteDb::prepare(const std::string &sql) {
|
|
45
|
+
sqlite3_stmt *stmt = nullptr;
|
|
46
|
+
int result = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr);
|
|
47
|
+
if (result != SQLITE_OK) {
|
|
48
|
+
throw std::runtime_error("Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)) + " SQL: " + sql);
|
|
49
|
+
}
|
|
50
|
+
return stmt;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
std::string SqliteDb::lastErrorMessage() const {
|
|
54
|
+
return db_ ? sqlite3_errmsg(db_) : "No database";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
int SqliteDb::lastErrorCode() const {
|
|
58
|
+
return db_ ? sqlite3_extended_errcode(db_) : 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
} // namespace pomegranate
|