diff --git a/android/jniLibs/arm64-v8a/libsql_experimental.a b/android/jniLibs/arm64-v8a/libsql_experimental.a index b693dfd3..e67171e1 100644 Binary files a/android/jniLibs/arm64-v8a/libsql_experimental.a and b/android/jniLibs/arm64-v8a/libsql_experimental.a differ diff --git a/android/jniLibs/armeabi-v7a/libsql_experimental.a b/android/jniLibs/armeabi-v7a/libsql_experimental.a index a09caa49..9669a835 100644 Binary files a/android/jniLibs/armeabi-v7a/libsql_experimental.a and b/android/jniLibs/armeabi-v7a/libsql_experimental.a differ diff --git a/android/jniLibs/x86/libsql_experimental.a b/android/jniLibs/x86/libsql_experimental.a index a76095cd..483ecca8 100644 Binary files a/android/jniLibs/x86/libsql_experimental.a and b/android/jniLibs/x86/libsql_experimental.a differ diff --git a/android/jniLibs/x86_64/libsql_experimental.a b/android/jniLibs/x86_64/libsql_experimental.a index 042b16fc..0e643ed2 100644 Binary files a/android/jniLibs/x86_64/libsql_experimental.a and b/android/jniLibs/x86_64/libsql_experimental.a differ diff --git a/cpp/DBHostObject.cpp b/cpp/DBHostObject.cpp index 07eba55a..1ce6bc6d 100644 --- a/cpp/DBHostObject.cpp +++ b/cpp/DBHostObject.cpp @@ -151,6 +151,23 @@ DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &url, create_jsi_functions(); } + +DBHostObject::DBHostObject(jsi::Runtime &rt, + std::shared_ptr invoker, + std::shared_ptr thread_pool, + std::string &db_name, std::string &path, + std::string &url, std::string &auth_token) + : db_name(url), jsCallInvoker(invoker), thread_pool(thread_pool), rt(rt) { + BridgeResult result = + opsqlite_libsql_open_sync(db_name, path, url, auth_token); + + if (result.type == SQLiteError) { + throw std::runtime_error(result.message); + } + + create_jsi_functions(); +} + #endif DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &base_path, diff --git a/cpp/DBHostObject.h b/cpp/DBHostObject.h index 70a49f45..57edd8e2 100644 --- a/cpp/DBHostObject.h +++ b/cpp/DBHostObject.h @@ -27,6 +27,7 @@ struct ReactiveQuery { class JSI_EXPORT DBHostObject : public jsi::HostObject { public: + // Constructor for local databases DBHostObject(jsi::Runtime &rt, std::string &base_path, std::shared_ptr js_call_invoker, std::shared_ptr thread_pool, std::string &db_name, @@ -34,9 +35,15 @@ class JSI_EXPORT DBHostObject : public jsi::HostObject { std::string &encryption_key); #ifdef OP_SQLITE_USE_LIBSQL + // Constructor for remoteOpen, purely for remote databases DBHostObject(jsi::Runtime &rt, std::string &url, std::string &auth_token, std::shared_ptr js_call_invoker, std::shared_ptr thread_pool); + + // Constructor for a local database with remote sync + DBHostObject(jsi::Runtime &rt, std::shared_ptr invoker, + std::shared_ptr thread_pool, std::string &db_name, + std::string &path, std::string &url, std::string &auth_token); #endif std::vector getPropertyNames(jsi::Runtime &rt); diff --git a/cpp/bindings.cpp b/cpp/bindings.cpp index 88d00cf5..7e9983b3 100644 --- a/cpp/bindings.cpp +++ b/cpp/bindings.cpp @@ -115,6 +115,34 @@ void install(jsi::Runtime &rt, std::shared_ptr invoker, rt, url, auth_token, invoker, thread_pool); return jsi::Object::createFromHostObject(rt, db); }); + + auto open_sync = HOSTFN("openSync", 1) { + jsi::Object options = args[0].asObject(rt); + std::string name = options.getProperty(rt, "name").asString(rt).utf8(rt); + std::string path = std::string(_base_path); + std::string url = options.getProperty(rt, "url").asString(rt).utf8(rt); + std::string auth_token = + options.getProperty(rt, "authToken").asString(rt).utf8(rt); + std::string location; + + if (options.hasProperty(rt, "location")) { + location = options.getProperty(rt, "location").asString(rt).utf8(rt); + } + + if (!location.empty()) { + if (location == ":memory:") { + path = ":memory:"; + } else if (location.rfind("/", 0) == 0) { + path = location; + } else { + path = path + "/" + location; + } + } + + std::shared_ptr db = std::make_shared( + rt, invoker, thread_pool, name, path, url, auth_token); + return jsi::Object::createFromHostObject(rt, db); + }); #endif jsi::Object module = jsi::Object(rt); @@ -123,6 +151,7 @@ void install(jsi::Runtime &rt, std::shared_ptr invoker, module.setProperty(rt, "isLibsql", std::move(is_libsql)); #ifdef OP_SQLITE_USE_LIBSQL module.setProperty(rt, "openRemote", std::move(open_remote)); + module.setProperty(rt, "openSync", std::move(open_sync)); #endif rt.global().setProperty(rt, "__OPSQLiteProxy", std::move(module)); diff --git a/cpp/libsql/bridge.cpp b/cpp/libsql/bridge.cpp index 30b85332..5cd79841 100644 --- a/cpp/libsql/bridge.cpp +++ b/cpp/libsql/bridge.cpp @@ -42,6 +42,35 @@ std::string opsqlite_get_db_path(std::string const &db_name, return location + "/" + db_name; } +BridgeResult opsqlite_libsql_open_sync(std::string const &name, + std::string const &base_path, + std::string const &url, + std::string const &auth_token) { + std::string path = opsqlite_get_db_path(name, base_path); + + int status = 0; + libsql_database_t db; + libsql_connection_t c; + const char *err = NULL; + + status = libsql_open_sync_with_webpki( + path.c_str(), url.c_str(), auth_token.c_str(), '1', nullptr, &db, &err); + + if (status != 0) { + return {.type = SQLiteError, .message = err}; + } + + status = libsql_connect(db, &c, &err); + + if (status != 0) { + return {.type = SQLiteError, .message = err}; + } + + db_map[name] = {.db = db, .c = c}; + + return {.type = SQLiteOk, .affectedRows = 0}; +} + BridgeResult opsqlite_libsql_open(std::string const &name, std::string const &last_path) { std::string path = opsqlite_get_db_path(name, last_path); @@ -75,7 +104,8 @@ BridgeResult opsqlite_libsql_open_remote(std::string const &url, libsql_connection_t c; const char *err = NULL; - status = libsql_open_remote(url.c_str(), auth_token.c_str(), &db, &err); + status = libsql_open_remote_with_webpki(url.c_str(), auth_token.c_str(), &db, + &err); if (status != 0) { return {.type = SQLiteError, .message = err}; diff --git a/cpp/libsql/bridge.h b/cpp/libsql/bridge.h index 0d4bd7d3..2236562e 100644 --- a/cpp/libsql/bridge.h +++ b/cpp/libsql/bridge.h @@ -33,6 +33,11 @@ BridgeResult opsqlite_libsql_open(std::string const &name, BridgeResult opsqlite_libsql_open_remote(std::string const &url, std::string const &auth_token); +BridgeResult opsqlite_libsql_open_sync(std::string const &name, + std::string const &path, + std::string const &url, + std::string const &auth_token); + BridgeResult opsqlite_libsql_close(std::string const &name); BridgeResult opsqlite_libsql_remove(std::string const &name, diff --git a/cpp/libsql/libsql.h b/cpp/libsql/libsql.h index 50afab81..c04baeed 100644 --- a/cpp/libsql/libsql.h +++ b/cpp/libsql/libsql.h @@ -56,12 +56,25 @@ int libsql_open_sync(const char *db_path, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_sync_with_webpki(const char *db_path, + const char *primary_url, + const char *auth_token, + char read_your_writes, + const char *encryption_key, + libsql_database_t *out_db, + const char **out_err_msg); + int libsql_open_ext(const char *url, libsql_database_t *out_db, const char **out_err_msg); int libsql_open_file(const char *url, libsql_database_t *out_db, const char **out_err_msg); int libsql_open_remote(const char *url, const char *auth_token, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_remote_with_webpki(const char *url, + const char *auth_token, + libsql_database_t *out_db, + const char **out_err_msg); + void libsql_close(libsql_database_t db); int libsql_connect(libsql_database_t db, libsql_connection_t *out_conn, const char **out_err_msg); diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ecd71612..7005bd64 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -7,12 +7,10 @@ PODS: - hermes-engine (0.74.0): - hermes-engine/Pre-built (= 0.74.0) - hermes-engine/Pre-built (0.74.0) - - op-sqlite (6.0.3): - - OpenSSL-Universal + - op-sqlite (6.0.4): - React - React-callinvoker - React-Core - - OpenSSL-Universal (3.1.5004) - RCT-Folly (2024.01.01.00): - boost - DoubleConversion @@ -1236,7 +1234,6 @@ DEPENDENCIES: SPEC REPOS: trunk: - - OpenSSL-Universal - SocketRocket EXTERNAL SOURCES: @@ -1361,8 +1358,7 @@ SPEC CHECKSUMS: fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 6eae7edb2f563ee41d7c1f91f4f2e57c26d8a5c3 - op-sqlite: 557247bb21ec97c23ff45a012cc1cbea2c42bcaf - OpenSSL-Universal: 0db2e81615ad95efc90ce13a638986858da38c0d + op-sqlite: d4e86e388f05494f1edc10a54a4b149687c52825 RCT-Folly: 045d6ecaa59d826c5736dfba0b2f4083ff8d79df RCTDeprecation: 3ca8b6c36bfb302e1895b72cfe7db0de0c92cd47 RCTRequired: 9fc183af555fd0c89a366c34c1ae70b7e03b1dc5 @@ -1416,4 +1412,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 0ab74fecad6ac2e35f8eab32fe5772c19d2015b2 -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.3 diff --git a/example/package.json b/example/package.json index b41d9bb5..ec33589a 100644 --- a/example/package.json +++ b/example/package.json @@ -57,10 +57,10 @@ }, "op-sqlite": { "sqlcipher": false, - "crsqlite": true, + "crsqlite": false, "performanceMode": "1", "iosSqlite": false, - "fts5": true, + "fts5": false, "libsql": false } } diff --git a/example/src/tests/queries.spec.ts b/example/src/tests/queries.spec.ts index 5bbe69f9..d1ff4b7a 100644 --- a/example/src/tests/queries.spec.ts +++ b/example/src/tests/queries.spec.ts @@ -3,6 +3,7 @@ import { isLibsql, open, openRemote, + openSync, type DB, type SQLBatchTuple, } from '@op-engineering/op-sqlite'; @@ -37,7 +38,7 @@ export function queriesTests() { describe('Queries tests', () => { if (isLibsql()) { - it('Test remote open', async () => { + it('Remote open a turso database', async () => { const remoteDb = openRemote({ url: 'libsql://foo-ospfranco.turso.io', authToken: @@ -46,7 +47,18 @@ export function queriesTests() { const res = remoteDb.execute('SELECT 1'); - console.warn(res); + expect(res.rowsAffected).to.equal(0); + }); + + it('Open a libsql database replicated to turso', async () => { + const remoteDb = openSync({ + url: 'libsql://foo-ospfranco.turso.io', + authToken: + 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhIjoicnciLCJpYXQiOjE3MTY5NTc5OTUsImlkIjoiZmJkNzZmMjYtZTliYy00MGJiLTlmYmYtMDczZjFmMjdjOGY4In0.U3cAWBOvcdiqoPN3MB81sco7x8CGOjjtZ1ZEf30uo2iPcAmOuJzcnAznmDlZ6SpQd4qzuJxE4mAIoRlOkpzgBQ', + name: 'my replica', + }); + + const res = remoteDb.execute('SELECT 1'); expect(res.rowsAffected).to.equal(0); }); } diff --git a/ios/libsql.xcframework/Info.plist b/ios/libsql.xcframework/Info.plist index fad6ff8c..90c2c42c 100644 --- a/ios/libsql.xcframework/Info.plist +++ b/ios/libsql.xcframework/Info.plist @@ -10,15 +10,18 @@ HeadersPath Headers LibraryIdentifier - ios-arm64 + ios-arm64_x86_64-simulator LibraryPath libsql_experimental.a SupportedArchitectures arm64 + x86_64 SupportedPlatform ios + SupportedPlatformVariant + simulator BinaryPath @@ -26,18 +29,15 @@ HeadersPath Headers LibraryIdentifier - ios-arm64_x86_64-simulator + ios-arm64 LibraryPath libsql_experimental.a SupportedArchitectures arm64 - x86_64 SupportedPlatform ios - SupportedPlatformVariant - simulator CFBundlePackageType diff --git a/ios/libsql.xcframework/ios-arm64/Headers/libsql.h b/ios/libsql.xcframework/ios-arm64/Headers/libsql.h index 50afab81..c04baeed 100644 --- a/ios/libsql.xcframework/ios-arm64/Headers/libsql.h +++ b/ios/libsql.xcframework/ios-arm64/Headers/libsql.h @@ -56,12 +56,25 @@ int libsql_open_sync(const char *db_path, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_sync_with_webpki(const char *db_path, + const char *primary_url, + const char *auth_token, + char read_your_writes, + const char *encryption_key, + libsql_database_t *out_db, + const char **out_err_msg); + int libsql_open_ext(const char *url, libsql_database_t *out_db, const char **out_err_msg); int libsql_open_file(const char *url, libsql_database_t *out_db, const char **out_err_msg); int libsql_open_remote(const char *url, const char *auth_token, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_remote_with_webpki(const char *url, + const char *auth_token, + libsql_database_t *out_db, + const char **out_err_msg); + void libsql_close(libsql_database_t db); int libsql_connect(libsql_database_t db, libsql_connection_t *out_conn, const char **out_err_msg); diff --git a/ios/libsql.xcframework/ios-arm64/libsql_experimental.a b/ios/libsql.xcframework/ios-arm64/libsql_experimental.a index 0d6b4082..50026f9b 100644 Binary files a/ios/libsql.xcframework/ios-arm64/libsql_experimental.a and b/ios/libsql.xcframework/ios-arm64/libsql_experimental.a differ diff --git a/ios/libsql.xcframework/ios-arm64_x86_64-simulator/Headers/libsql.h b/ios/libsql.xcframework/ios-arm64_x86_64-simulator/Headers/libsql.h index 50afab81..c04baeed 100644 --- a/ios/libsql.xcframework/ios-arm64_x86_64-simulator/Headers/libsql.h +++ b/ios/libsql.xcframework/ios-arm64_x86_64-simulator/Headers/libsql.h @@ -56,12 +56,25 @@ int libsql_open_sync(const char *db_path, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_sync_with_webpki(const char *db_path, + const char *primary_url, + const char *auth_token, + char read_your_writes, + const char *encryption_key, + libsql_database_t *out_db, + const char **out_err_msg); + int libsql_open_ext(const char *url, libsql_database_t *out_db, const char **out_err_msg); int libsql_open_file(const char *url, libsql_database_t *out_db, const char **out_err_msg); int libsql_open_remote(const char *url, const char *auth_token, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_remote_with_webpki(const char *url, + const char *auth_token, + libsql_database_t *out_db, + const char **out_err_msg); + void libsql_close(libsql_database_t db); int libsql_connect(libsql_database_t db, libsql_connection_t *out_conn, const char **out_err_msg); diff --git a/ios/libsql.xcframework/ios-arm64_x86_64-simulator/libsql_experimental.a b/ios/libsql.xcframework/ios-arm64_x86_64-simulator/libsql_experimental.a index f8ba3ba8..46fd05da 100644 Binary files a/ios/libsql.xcframework/ios-arm64_x86_64-simulator/libsql_experimental.a and b/ios/libsql.xcframework/ios-arm64_x86_64-simulator/libsql_experimental.a differ diff --git a/package.json b/package.json index 7fb18d05..9d2676fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@op-engineering/op-sqlite", - "version": "6.0.4", + "version": "6.0.5", "description": "Next generation SQLite for React Native", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/src/index.ts b/src/index.ts index 95842322..e8d9ff72 100644 --- a/src/index.ts +++ b/src/index.ts @@ -195,6 +195,12 @@ type OPSQLiteProxy = { encryptionKey?: string; }) => DB; openRemote: (options: { url: string; authToken: string }) => DB; + openSync: (options: { + url: string; + authToken: string; + name: string; + location?: string; + }) => DB; isSQLCipher: () => boolean; isLibsql: () => boolean; }; @@ -220,14 +226,7 @@ function enhanceQueryResult(result: QueryResult): void { } } -// TODO de-dupe this function with open -export const openRemote = (options: { url: string; authToken: string }): DB => { - if (!isLibsql()) { - throw new Error('This function is only available for libsql'); - } - - const db = OPSQLite.openRemote(options); - +function enhanceDB(db: DB, options: any): DB { const lock = { queue: [] as PendingTransaction[], inProgress: false, @@ -416,201 +415,42 @@ export const openRemote = (options: { url: string; authToken: string }): DB => { }; return enhancedDb; -}; +} -export const open = (options: { +export const openSync = (options: { + url: string; + authToken: string; name: string; location?: string; - encryptionKey?: string; }): DB => { - const db = OPSQLite.open(options); - - const lock = { - queue: [] as PendingTransaction[], - inProgress: false, - }; - - const startNextTransaction = () => { - if (lock.inProgress) { - // Transaction is already in process bail out - return; - } - - if (lock.queue.length) { - lock.inProgress = true; - const tx = lock.queue.shift(); - - if (!tx) { - throw new Error('Could not get a operation on database'); - } - - setImmediate(() => { - tx.start(); - }); - } - }; - - // spreading the object is not working, so we need to do it manually - let enhancedDb = { - delete: db.delete, - attach: db.attach, - detach: db.detach, - executeBatch: db.executeBatch, - executeBatchAsync: db.executeBatchAsync, - - loadFile: db.loadFile, - updateHook: db.updateHook, - commitHook: db.commitHook, - rollbackHook: db.rollbackHook, - loadExtension: db.loadExtension, - executeRawAsync: db.executeRawAsync, - getDbPath: db.getDbPath, - reactiveExecute: db.reactiveExecute, - close: () => { - db.close(); - delete locks[options.name]; - }, - execute: (query: string, params?: any[] | undefined): QueryResult => { - const sanitizedParams = params?.map((p) => { - if (ArrayBuffer.isView(p)) { - return p.buffer; - } - - return p; - }); - - const result = db.execute(query, sanitizedParams); - enhanceQueryResult(result); - return result; - }, - executeAsync: async ( - query: string, - params?: any[] | undefined - ): Promise => { - const sanitizedParams = params?.map((p) => { - if (ArrayBuffer.isView(p)) { - return p.buffer; - } - - return p; - }); - - const result = await db.executeAsync(query, sanitizedParams); - enhanceQueryResult(result); - return result; - }, - prepareStatement: (query: string) => { - const stmt = db.prepareStatement(query); - - return { - bind: (params: any[]) => { - const sanitizedParams = params.map((p) => { - if (ArrayBuffer.isView(p)) { - return p.buffer; - } - - return p; - }); - - stmt.bind(sanitizedParams); - }, - execute: () => { - const res = stmt.execute(); - enhanceQueryResult(res); - return res; - }, - }; - }, - transaction: async ( - fn: (tx: Transaction) => Promise - ): Promise => { - let isFinalized = false; - - // Local transaction context object implementation - const execute = (query: string, params?: any[]): QueryResult => { - if (isFinalized) { - throw Error( - `OP-Sqlite Error: Database: ${options.name}. Cannot execute query on finalized transaction` - ); - } - return enhancedDb.execute(query, params); - }; - - const executeAsync = (query: string, params?: any[] | undefined) => { - if (isFinalized) { - throw Error( - `OP-Sqlite Error: Database: ${options.name}. Cannot execute query on finalized transaction` - ); - } - return enhancedDb.executeAsync(query, params); - }; - - const commit = () => { - if (isFinalized) { - throw Error( - `OP-Sqlite Error: Database: ${options.name}. Cannot execute query on finalized transaction` - ); - } - const result = enhancedDb.execute('COMMIT;'); - isFinalized = true; - return result; - }; - - const rollback = () => { - if (isFinalized) { - throw Error( - `OP-Sqlite Error: Database: ${options.name}. Cannot execute query on finalized transaction` - ); - } - const result = enhancedDb.execute('ROLLBACK;'); - isFinalized = true; - return result; - }; + if (!isLibsql()) { + throw new Error('This function is only available for libsql'); + } - async function run() { - try { - await enhancedDb.executeAsync('BEGIN TRANSACTION;'); + const db = OPSQLite.openSync(options); + const enhancedDb = enhanceDB(db, options); - await fn({ - commit, - execute, - executeAsync, - rollback, - }); + return enhancedDb; +}; - if (!isFinalized) { - commit(); - } - } catch (executionError) { - console.warn('transaction error', executionError); - if (!isFinalized) { - try { - rollback(); - } catch (rollbackError) { - throw rollbackError; - } - } +export const openRemote = (options: { url: string; authToken: string }): DB => { + if (!isLibsql()) { + throw new Error('This function is only available for libsql'); + } - throw executionError; - } finally { - lock.inProgress = false; - isFinalized = false; - startNextTransaction(); - } - } + const db = OPSQLite.openRemote(options); + const enhancedDb = enhanceDB(db, options); - return await new Promise((resolve, reject) => { - const tx: PendingTransaction = { - start: () => { - run().then(resolve).catch(reject); - }, - }; + return enhancedDb; +}; - lock.queue.push(tx); - startNextTransaction(); - }); - }, - }; +export const open = (options: { + name: string; + location?: string; + encryptionKey?: string; +}): DB => { + const db = OPSQLite.open(options); + const enhancedDb = enhanceDB(db, options); return enhancedDb; };