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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +38 -0
  3. package/PomegranateDB.podspec +67 -0
  4. package/README.md +122 -0
  5. package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.d.ts +34 -0
  6. package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.js +155 -0
  7. package/dist/adapters/expo-sqlite/index.d.ts +2 -0
  8. package/dist/adapters/expo-sqlite/index.js +6 -0
  9. package/dist/adapters/index.d.ts +7 -0
  10. package/dist/adapters/index.js +13 -0
  11. package/dist/adapters/loki/LokiAdapter.d.ts +100 -0
  12. package/dist/adapters/loki/LokiAdapter.js +144 -0
  13. package/dist/adapters/loki/index.d.ts +6 -0
  14. package/dist/adapters/loki/index.js +12 -0
  15. package/dist/adapters/loki/worker/LokiDispatcher.d.ts +21 -0
  16. package/dist/adapters/loki/worker/LokiDispatcher.js +63 -0
  17. package/dist/adapters/loki/worker/LokiExecutor.d.ts +96 -0
  18. package/dist/adapters/loki/worker/LokiExecutor.js +462 -0
  19. package/dist/adapters/loki/worker/SynchronousWorker.d.ts +22 -0
  20. package/dist/adapters/loki/worker/SynchronousWorker.js +76 -0
  21. package/dist/adapters/loki/worker/loki.worker.d.ts +14 -0
  22. package/dist/adapters/loki/worker/loki.worker.js +112 -0
  23. package/dist/adapters/loki/worker/types.d.ts +44 -0
  24. package/dist/adapters/loki/worker/types.js +11 -0
  25. package/dist/adapters/native-sqlite/NativeSQLiteDriver.d.ts +55 -0
  26. package/dist/adapters/native-sqlite/NativeSQLiteDriver.js +145 -0
  27. package/dist/adapters/native-sqlite/index.d.ts +2 -0
  28. package/dist/adapters/native-sqlite/index.js +6 -0
  29. package/dist/adapters/op-sqlite/OpSQLiteDriver.d.ts +49 -0
  30. package/dist/adapters/op-sqlite/OpSQLiteDriver.js +140 -0
  31. package/dist/adapters/op-sqlite/index.d.ts +2 -0
  32. package/dist/adapters/op-sqlite/index.js +6 -0
  33. package/dist/adapters/sqlite/SQLiteAdapter.d.ts +70 -0
  34. package/dist/adapters/sqlite/SQLiteAdapter.js +264 -0
  35. package/dist/adapters/sqlite/index.d.ts +2 -0
  36. package/dist/adapters/sqlite/index.js +6 -0
  37. package/dist/adapters/sqlite/sql.d.ts +35 -0
  38. package/dist/adapters/sqlite/sql.js +258 -0
  39. package/dist/adapters/types.d.ts +93 -0
  40. package/dist/adapters/types.js +9 -0
  41. package/dist/collection/Collection.d.ts +103 -0
  42. package/dist/collection/Collection.js +245 -0
  43. package/dist/collection/index.d.ts +2 -0
  44. package/dist/collection/index.js +6 -0
  45. package/dist/database/Database.d.ts +128 -0
  46. package/dist/database/Database.js +245 -0
  47. package/dist/database/index.d.ts +2 -0
  48. package/dist/database/index.js +6 -0
  49. package/dist/encryption/index.d.ts +62 -0
  50. package/dist/encryption/index.js +276 -0
  51. package/dist/encryption/nodeCrypto.d.ts +18 -0
  52. package/dist/encryption/nodeCrypto.js +25 -0
  53. package/dist/encryption/nodeCrypto.native.d.ts +13 -0
  54. package/dist/encryption/nodeCrypto.native.js +26 -0
  55. package/dist/expo.d.ts +12 -0
  56. package/dist/expo.js +32 -0
  57. package/dist/hooks/index.d.ts +115 -0
  58. package/dist/hooks/index.js +285 -0
  59. package/dist/index.d.ts +29 -0
  60. package/dist/index.js +57 -0
  61. package/dist/model/Model.d.ts +92 -0
  62. package/dist/model/Model.js +251 -0
  63. package/dist/model/index.d.ts +2 -0
  64. package/dist/model/index.js +7 -0
  65. package/dist/observable/Subject.d.ts +60 -0
  66. package/dist/observable/Subject.js +132 -0
  67. package/dist/observable/index.d.ts +2 -0
  68. package/dist/observable/index.js +10 -0
  69. package/dist/query/QueryBuilder.d.ts +51 -0
  70. package/dist/query/QueryBuilder.js +165 -0
  71. package/dist/query/index.d.ts +2 -0
  72. package/dist/query/index.js +7 -0
  73. package/dist/query/types.d.ts +60 -0
  74. package/dist/query/types.js +9 -0
  75. package/dist/schema/builder.d.ts +68 -0
  76. package/dist/schema/builder.js +168 -0
  77. package/dist/schema/index.d.ts +2 -0
  78. package/dist/schema/index.js +7 -0
  79. package/dist/schema/types.d.ts +108 -0
  80. package/dist/schema/types.js +9 -0
  81. package/dist/sync/index.d.ts +2 -0
  82. package/dist/sync/index.js +6 -0
  83. package/dist/sync/sync.d.ts +15 -0
  84. package/dist/sync/sync.js +182 -0
  85. package/dist/sync/types.d.ts +41 -0
  86. package/dist/sync/types.js +6 -0
  87. package/dist/utils/index.d.ts +45 -0
  88. package/dist/utils/index.js +99 -0
  89. package/expo-plugin/index.d.ts +68 -0
  90. package/expo-plugin/index.js +83 -0
  91. package/native/android-jsi/build.gradle +45 -0
  92. package/native/android-jsi/src/main/AndroidManifest.xml +2 -0
  93. package/native/android-jsi/src/main/cpp/CMakeLists.txt +73 -0
  94. package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.cpp +107 -0
  95. package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.h +16 -0
  96. package/native/android-jsi/src/main/cpp/JSIInstaller.cpp +27 -0
  97. package/native/android-jsi/src/main/java/com/pomegranate/jsi/JSIInstaller.kt +43 -0
  98. package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIModule.kt +39 -0
  99. package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIPackage.kt +17 -0
  100. package/native/ios/DatabasePlatformIOS.mm +83 -0
  101. package/native/ios/PomegranateJSI.h +15 -0
  102. package/native/ios/PomegranateJSI.mm +59 -0
  103. package/native/shared/Database.cpp +283 -0
  104. package/native/shared/Database.h +84 -0
  105. package/native/shared/Sqlite.cpp +61 -0
  106. package/native/shared/Sqlite.h +67 -0
  107. package/native/shared/sqlite3/sqlite3.c +260493 -0
  108. package/native/shared/sqlite3/sqlite3.h +13583 -0
  109. package/package.json +127 -0
  110. 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