From 01f62700ef5c36802fbc4a55f4d7bec6a324ecbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nishan=20=28o=5E=E2=96=BD=5Eo=29?= Date: Wed, 4 Mar 2026 12:59:35 +0530 Subject: [PATCH 1/6] [application][android] Fix `PromiseAlreadySettledException` crash in `getInstallReferrerAsync` (#43419) # Why https://github.com/expo/expo/issues/43094 As OP mentioned, this issue is not consistently reproducible but from the stack it looks like something (`doDeath` callback) is triggering the `onInstallReferrerServiceDisconnected` on an already settled promise. # How Return early if the promise was already settled. # Test Plan No crash repro, but existing functionality should not be affected. # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/expo-application/CHANGELOG.md | 2 ++ .../modules/application/ApplicationModule.kt | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/expo-application/CHANGELOG.md b/packages/expo-application/CHANGELOG.md index f9dae5e035eb7a..c3bc92a03afd96 100644 --- a/packages/expo-application/CHANGELOG.md +++ b/packages/expo-application/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- [Android] Fix `PromiseAlreadySettledException` crash in `getInstallReferrerAsync` when `onInstallReferrerServiceDisconnected` gets called after promise settlement. ([#43419](https://github.com/expo/expo/pull/43419) by [@nishan](https://github.com/intergalacticspacehighway)) + ### 💡 Others ## 55.0.8 — 2026-02-25 diff --git a/packages/expo-application/android/src/main/java/expo/modules/application/ApplicationModule.kt b/packages/expo-application/android/src/main/java/expo/modules/application/ApplicationModule.kt index 3d0622b26cd48e..b151a60dc628fd 100644 --- a/packages/expo-application/android/src/main/java/expo/modules/application/ApplicationModule.kt +++ b/packages/expo-application/android/src/main/java/expo/modules/application/ApplicationModule.kt @@ -64,11 +64,14 @@ class ApplicationModule : Module() { AsyncFunction("getInstallReferrerAsync") { promise: Promise -> val installReferrer = StringBuilder() + var isSettled = false val referrerClient = InstallReferrerClient.newBuilder(context).build() referrerClient.startConnection(object : InstallReferrerStateListener { override fun onInstallReferrerSetupFinished(responseCode: Int) { + if (isSettled) return + when (responseCode) { InstallReferrerClient.InstallReferrerResponse.OK -> { // Connection established and response received @@ -78,22 +81,33 @@ class ApplicationModule : Module() { } catch (e: RemoteException) { promise.reject("ERR_APPLICATION_INSTALL_REFERRER_REMOTE_EXCEPTION", "RemoteException getting install referrer information. This may happen if the process hosting the remote object is no longer available.", e) return + } finally { + isSettled = true } promise.resolve(installReferrer.toString()) } - InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> // API not available in the current Play Store app + InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> { // API not available in the current Play Store app + isSettled = true promise.reject("ERR_APPLICATION_INSTALL_REFERRER_UNAVAILABLE", "The current Play Store app doesn't provide the installation referrer API, or the Play Store may not be installed.", null) + } - InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE -> // Connection could not be established + InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE -> { // Connection could not be established + isSettled = true promise.reject("ERR_APPLICATION_INSTALL_REFERRER", "General error retrieving the install referrer: response code $responseCode", null) + } - else -> promise.reject("ERR_APPLICATION_INSTALL_REFERRER", "General error retrieving the install referrer: response code $responseCode", null) + else -> { + isSettled = true + promise.reject("ERR_APPLICATION_INSTALL_REFERRER", "General error retrieving the install referrer: response code $responseCode", null) + } } referrerClient.endConnection() } override fun onInstallReferrerServiceDisconnected() { + if (isSettled) return + isSettled = true promise.reject("ERR_APPLICATION_INSTALL_REFERRER_SERVICE_DISCONNECTED", "Connection to install referrer service was lost.", null) } }) From 6aa58eb0a63f79e3f3dd2a6ce3d89535d3ec30c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Klocek?= Date: Wed, 4 Mar 2026 09:23:36 +0100 Subject: [PATCH 2/6] [sqlite] Use ArrayBuffers for session changesets (#42638) # Why Replace copying `Uint8Array` with zero-copy `ArrayBuffer.` # How - Replaced native argument and return types - In C++ replaced `JArrayByte` with direct `JByteBuffer` - reduces JNI overhead - Used `JavaScriptArrayBuffer` for sync and `NativeArrayBuffer` (copying but thread-safe) for async function arguments. - Kept user-facing JS types untouched - For web module, transformed back to Uint8Array # Test Plan - SQLite test suite - both Android and iOS - Manual testing of all updated functions # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/expo-sqlite/CHANGELOG.md | 2 + .../src/main/cpp/NativeSessionBinding.cpp | 44 ++++++++--------- .../src/main/cpp/NativeSessionBinding.h | 11 +++-- .../modules/sqlite/NativeSessionBinding.kt | 9 ++-- .../java/expo/modules/sqlite/SQLiteModule.kt | 33 ++++++++----- packages/expo-sqlite/build/NativeSession.d.ts | 17 ++++--- .../expo-sqlite/build/NativeSession.d.ts.map | 2 +- .../expo-sqlite/build/NativeSession.js.map | 2 +- .../expo-sqlite/build/SQLiteSession.d.ts.map | 2 +- packages/expo-sqlite/build/SQLiteSession.js | 24 +++++---- .../expo-sqlite/build/SQLiteSession.js.map | 2 +- packages/expo-sqlite/ios/SQLiteModule.swift | 36 +++++++------- packages/expo-sqlite/src/NativeSession.ts | 28 +++++++---- packages/expo-sqlite/src/SQLiteSession.ts | 29 +++++++---- packages/expo-sqlite/web/SQLiteModule.node.ts | 30 ++++++------ packages/expo-sqlite/web/SQLiteModule.ts | 49 ++++++++++++------- 16 files changed, 185 insertions(+), 135 deletions(-) diff --git a/packages/expo-sqlite/CHANGELOG.md b/packages/expo-sqlite/CHANGELOG.md index dc2167ca0be914..7487eafbae7e80 100644 --- a/packages/expo-sqlite/CHANGELOG.md +++ b/packages/expo-sqlite/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- Session changesets now use native `ArrayBuffer`s. ([#42638](https://github.com/expo/expo/pull/42638) by [@barthap](https://github.com/barthap)) + ## 55.0.10 — 2026-02-25 ### 💡 Others diff --git a/packages/expo-sqlite/android/src/main/cpp/NativeSessionBinding.cpp b/packages/expo-sqlite/android/src/main/cpp/NativeSessionBinding.cpp index ffd425ba386150..37758643f6f710 100644 --- a/packages/expo-sqlite/android/src/main/cpp/NativeSessionBinding.cpp +++ b/packages/expo-sqlite/android/src/main/cpp/NativeSessionBinding.cpp @@ -57,7 +57,7 @@ void NativeSessionBinding::sqlite3session_delete() { ::exsqlite3session_delete(session); } -jni::local_ref +jni::local_ref NativeSessionBinding::sqlite3session_changeset() { int size = 0; void *buffer = nullptr; @@ -66,15 +66,15 @@ NativeSessionBinding::sqlite3session_changeset() { return nullptr; } if (!buffer) { - return jni::JArrayByte::newArray(0); + return jni::JByteBuffer::allocateDirect(0); } - auto byteArray = jni::JArrayByte::newArray(size); - byteArray->setRegion(0, size, reinterpret_cast(buffer)); + auto byteArray = jni::JByteBuffer::allocateDirect(size); + memcpy(byteArray->getDirectAddress(), buffer, size); ::exsqlite3_free(buffer); return byteArray; } -jni::local_ref +jni::local_ref NativeSessionBinding::sqlite3session_changeset_inverted() { int inSize = 0; void *inBuffer = nullptr; @@ -83,7 +83,7 @@ NativeSessionBinding::sqlite3session_changeset_inverted() { return nullptr; } if (!inBuffer) { - return jni::JArrayByte::newArray(0); + return jni::JByteBuffer::allocateDirect(0); } int outSize = 0; @@ -95,47 +95,45 @@ NativeSessionBinding::sqlite3session_changeset_inverted() { } if (!outBuffer) { ::exsqlite3_free(inBuffer); - return jni::JArrayByte::newArray(0); + return jni::JByteBuffer::allocateDirect(0); } - auto byteArray = jni::JArrayByte::newArray(outSize); - byteArray->setRegion(0, outSize, - reinterpret_cast(outBuffer)); + auto byteArray = jni::JByteBuffer::allocateDirect(outSize); + memcpy(byteArray->getDirectAddress(), outBuffer, outSize); ::exsqlite3_free(outBuffer); return byteArray; } int NativeSessionBinding::sqlite3changeset_apply( jni::alias_ref db, - jni::alias_ref changeset) { - int size = static_cast(changeset->size()); - auto buffer = changeset->getRegion(0, size); + jni::alias_ref changeset) { + int size = static_cast(changeset->getDirectSize()); + auto buffer = changeset->getDirectAddress(); auto onConflict = [](void *pCtx, int eConflict, ::exsqlite3_changeset_iter *pIter) -> int { return SQLITE_CHANGESET_REPLACE; }; - return ::exsqlite3changeset_apply(db->cthis()->rawdb(), size, buffer.get(), + return ::exsqlite3changeset_apply(db->cthis()->rawdb(), size, buffer, nullptr, onConflict, nullptr); } -jni::local_ref NativeSessionBinding::sqlite3changeset_invert( - jni::alias_ref changeset) { - int inSize = static_cast(changeset->size()); - auto inBuffer = changeset->getRegion(0, inSize); +jni::local_ref NativeSessionBinding::sqlite3changeset_invert( + jni::alias_ref changeset) { + int inSize = static_cast(changeset->getDirectSize()); + auto inBuffer = changeset->getDirectAddress(); int outSize = 0; void *outBuffer = nullptr; int result = - ::exsqlite3changeset_invert(inSize, inBuffer.get(), &outSize, &outBuffer); + ::exsqlite3changeset_invert(inSize, inBuffer, &outSize, &outBuffer); if (result != SQLITE_OK) { return nullptr; } if (!outBuffer) { - return jni::JArrayByte::newArray(0); + return jni::JByteBuffer::allocateDirect(0); } - auto byteArray = jni::JArrayByte::newArray(outSize); - byteArray->setRegion(0, outSize, - reinterpret_cast(outBuffer)); + auto byteArray = jni::JByteBuffer::allocateDirect(outSize); + memcpy(byteArray->getDirectAddress(), outBuffer, outSize); ::exsqlite3_free(outBuffer); return byteArray; } diff --git a/packages/expo-sqlite/android/src/main/cpp/NativeSessionBinding.h b/packages/expo-sqlite/android/src/main/cpp/NativeSessionBinding.h index 60902800038808..2803ee38386737 100644 --- a/packages/expo-sqlite/android/src/main/cpp/NativeSessionBinding.h +++ b/packages/expo-sqlite/android/src/main/cpp/NativeSessionBinding.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include "NativeDatabaseBinding.h" @@ -26,13 +27,13 @@ class NativeSessionBinding : public jni::HybridClass { int sqlite3session_attach(jni::alias_ref tableName); int sqlite3session_enable(bool enabled); void sqlite3session_delete(); - jni::local_ref sqlite3session_changeset(); - jni::local_ref sqlite3session_changeset_inverted(); + jni::local_ref sqlite3session_changeset(); + jni::local_ref sqlite3session_changeset_inverted(); int sqlite3changeset_apply( jni::alias_ref db, - jni::alias_ref changeset); - jni::local_ref - sqlite3changeset_invert(jni::alias_ref changeset); + jni::alias_ref changeset); + jni::local_ref + sqlite3changeset_invert(jni::alias_ref changeset); private: explicit NativeSessionBinding( diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/NativeSessionBinding.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/NativeSessionBinding.kt index cd939bacfdd3f1..cc0fdfec1f6fd0 100644 --- a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/NativeSessionBinding.kt +++ b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/NativeSessionBinding.kt @@ -5,6 +5,7 @@ package expo.modules.sqlite import com.facebook.jni.HybridData import expo.modules.core.interfaces.DoNotStrip import java.io.Closeable +import java.nio.ByteBuffer @Suppress("KotlinJniMissingFunction", "FunctionName") @DoNotStrip @@ -26,10 +27,10 @@ internal class NativeSessionBinding : Closeable { external fun sqlite3session_attach(tableName: String?): Int external fun sqlite3session_enable(enabled: Boolean): Int external fun sqlite3session_delete() - external fun sqlite3session_changeset(): ByteArray? - external fun sqlite3session_changeset_inverted(): ByteArray? - external fun sqlite3changeset_apply(db: NativeDatabaseBinding, changeset: ByteArray): Int - external fun sqlite3changeset_invert(changeset: ByteArray): ByteArray? + external fun sqlite3session_changeset(): ByteBuffer? + external fun sqlite3session_changeset_inverted(): ByteBuffer? + external fun sqlite3changeset_apply(db: NativeDatabaseBinding, changeset: ByteBuffer): Int + external fun sqlite3changeset_invert(changeset: ByteBuffer): ByteBuffer? // endregion diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt index ac800ac162c8c0..7d4102c6aaafa9 100644 --- a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt +++ b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt @@ -7,6 +7,9 @@ import androidx.core.net.toFile import androidx.core.net.toUri import androidx.core.os.bundleOf import expo.modules.kotlin.exception.Exceptions +import expo.modules.kotlin.jni.ArrayBuffer +import expo.modules.kotlin.jni.JavaScriptArrayBuffer +import expo.modules.kotlin.jni.NativeArrayBuffer import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition import kotlinx.coroutines.CoroutineScope @@ -305,17 +308,17 @@ class SQLiteModule : Module() { return@Function sessionCreateInvertedChangeset(database, session) } - AsyncFunction("applyChangesetAsync") { session: NativeSession, database: NativeDatabase, changeset: ByteArray -> + AsyncFunction("applyChangesetAsync") { session: NativeSession, database: NativeDatabase, changeset: NativeArrayBuffer -> sessionApplyChangeset(database, session, changeset) }.runOnQueue(moduleCoroutineScope) - Function("applyChangesetSync") { session: NativeSession, database: NativeDatabase, changeset: ByteArray -> + Function("applyChangesetSync") { session: NativeSession, database: NativeDatabase, changeset: JavaScriptArrayBuffer -> sessionApplyChangeset(database, session, changeset) } - AsyncFunction("invertChangesetAsync") { session: NativeSession, database: NativeDatabase, changeset: ByteArray -> + AsyncFunction("invertChangesetAsync") { session: NativeSession, database: NativeDatabase, changeset: NativeArrayBuffer -> return@AsyncFunction sessionInvertChangeset(database, session, changeset) }.runOnQueue(moduleCoroutineScope) - Function("invertChangesetSync") { session: NativeSession, database: NativeDatabase, changeset: ByteArray -> + Function("invertChangesetSync") { session: NativeSession, database: NativeDatabase, changeset: JavaScriptArrayBuffer -> return@Function sessionInvertChangeset(database, session, changeset) } } @@ -643,32 +646,38 @@ class SQLiteModule : Module() { } @Throws(AccessClosedResourceException::class, SQLiteErrorException::class) - private fun sessionCreateChangeset(database: NativeDatabase, session: NativeSession): ByteArray { + private fun sessionCreateChangeset(database: NativeDatabase, session: NativeSession): NativeArrayBuffer { maybeThrowForClosedDatabase(database) - return session.ref.sqlite3session_changeset() + val byteBuffer = session.ref.sqlite3session_changeset() ?: throw SQLiteErrorException(database.ref.convertSqlLiteErrorToString()) + + return NativeArrayBuffer(byteBuffer) } @Throws(AccessClosedResourceException::class, SQLiteErrorException::class) - private fun sessionCreateInvertedChangeset(database: NativeDatabase, session: NativeSession): ByteArray { + private fun sessionCreateInvertedChangeset(database: NativeDatabase, session: NativeSession): NativeArrayBuffer { maybeThrowForClosedDatabase(database) - return session.ref.sqlite3session_changeset_inverted() + val byteBuffer = session.ref.sqlite3session_changeset_inverted() ?: throw SQLiteErrorException(database.ref.convertSqlLiteErrorToString()) + + return NativeArrayBuffer(byteBuffer) } @Throws(AccessClosedResourceException::class, SQLiteErrorException::class) - private fun sessionApplyChangeset(database: NativeDatabase, session: NativeSession, changeset: ByteArray) { + private fun sessionApplyChangeset(database: NativeDatabase, session: NativeSession, changeset: ArrayBuffer) { maybeThrowForClosedDatabase(database) - if (session.ref.sqlite3changeset_apply(database.ref, changeset) != NativeDatabaseBinding.SQLITE_OK) { + if (session.ref.sqlite3changeset_apply(database.ref, changeset.toDirectBuffer()) != NativeDatabaseBinding.SQLITE_OK) { throw SQLiteErrorException(database.ref.convertSqlLiteErrorToString()) } } @Throws(AccessClosedResourceException::class, SQLiteErrorException::class) - private fun sessionInvertChangeset(database: NativeDatabase, session: NativeSession, changeset: ByteArray): ByteArray { + private fun sessionInvertChangeset(database: NativeDatabase, session: NativeSession, changeset: ArrayBuffer): NativeArrayBuffer { maybeThrowForClosedDatabase(database) - return session.ref.sqlite3changeset_invert(changeset) + val byteBuffer = session.ref.sqlite3changeset_invert(changeset.toDirectBuffer()) ?: throw SQLiteErrorException(database.ref.convertSqlLiteErrorToString()) + + return NativeArrayBuffer(byteBuffer) } // endregion diff --git a/packages/expo-sqlite/build/NativeSession.d.ts b/packages/expo-sqlite/build/NativeSession.d.ts index dfe83c6583360c..da6c6795291023 100644 --- a/packages/expo-sqlite/build/NativeSession.d.ts +++ b/packages/expo-sqlite/build/NativeSession.d.ts @@ -2,21 +2,22 @@ * A type that represents a changeset. */ export type Changeset = Uint8Array; +export type NativeChangeset = ArrayBuffer; export type SQLiteAnyDatabase = any; export declare class NativeSession { attachAsync(database: SQLiteAnyDatabase, table: string | null): Promise; enableAsync(database: SQLiteAnyDatabase, enabled: boolean): Promise; closeAsync(database: SQLiteAnyDatabase): Promise; - createChangesetAsync(database: SQLiteAnyDatabase): Promise; - createInvertedChangesetAsync(database: SQLiteAnyDatabase): Promise; - applyChangesetAsync(database: SQLiteAnyDatabase, changeset: Changeset): Promise; - invertChangesetAsync(database: SQLiteAnyDatabase, changeset: Changeset): Promise; + createChangesetAsync(database: SQLiteAnyDatabase): Promise; + createInvertedChangesetAsync(database: SQLiteAnyDatabase): Promise; + applyChangesetAsync(database: SQLiteAnyDatabase, changeset: Changeset | NativeChangeset): Promise; + invertChangesetAsync(database: SQLiteAnyDatabase, changeset: Changeset | NativeChangeset): Promise; attachSync(database: SQLiteAnyDatabase, table: string | null): void; enableSync(database: SQLiteAnyDatabase, enabled: boolean): void; closeSync(database: SQLiteAnyDatabase): void; - createChangesetSync(database: SQLiteAnyDatabase): Changeset; - createInvertedChangesetSync(database: SQLiteAnyDatabase): Changeset; - applyChangesetSync(database: SQLiteAnyDatabase, changeset: Changeset): void; - invertChangesetSync(database: SQLiteAnyDatabase, changeset: Changeset): Changeset; + createChangesetSync(database: SQLiteAnyDatabase): NativeChangeset; + createInvertedChangesetSync(database: SQLiteAnyDatabase): NativeChangeset; + applyChangesetSync(database: SQLiteAnyDatabase, changeset: Changeset | NativeChangeset): void; + invertChangesetSync(database: SQLiteAnyDatabase, changeset: Changeset | NativeChangeset): NativeChangeset; } //# sourceMappingURL=NativeSession.d.ts.map \ No newline at end of file diff --git a/packages/expo-sqlite/build/NativeSession.d.ts.map b/packages/expo-sqlite/build/NativeSession.d.ts.map index ab4c52242803ef..39be695dd938f1 100644 --- a/packages/expo-sqlite/build/NativeSession.d.ts.map +++ b/packages/expo-sqlite/build/NativeSession.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"NativeSession.d.ts","sourceRoot":"","sources":["../src/NativeSession.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC;AAEnC,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAEpC,MAAM,CAAC,OAAO,OAAO,aAAa;IAGzB,WAAW,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7E,WAAW,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACzE,UAAU,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAEtD,oBAAoB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IACrE,4BAA4B,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IAC7E,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IACrF,oBAAoB,CACzB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,SAAS,CAAC;IAMd,UAAU,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IACnE,UAAU,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAC/D,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAE5C,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,SAAS;IAC3D,2BAA2B,CAAC,QAAQ,EAAE,iBAAiB,GAAG,SAAS;IACnE,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI;IAC3E,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,GAAG,SAAS;CAGzF"} \ No newline at end of file +{"version":3,"file":"NativeSession.d.ts","sourceRoot":"","sources":["../src/NativeSession.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC;AACnC,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC;AAE1C,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAEpC,MAAM,CAAC,OAAO,OAAO,aAAa;IAGzB,WAAW,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7E,WAAW,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACzE,UAAU,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAEtD,oBAAoB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAC3E,4BAA4B,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IACnF,mBAAmB,CACxB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,GAAG,eAAe,GACrC,OAAO,CAAC,IAAI,CAAC;IACT,oBAAoB,CACzB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,GAAG,eAAe,GACrC,OAAO,CAAC,eAAe,CAAC;IAMpB,UAAU,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IACnE,UAAU,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAC/D,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAE5C,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,eAAe;IACjE,2BAA2B,CAAC,QAAQ,EAAE,iBAAiB,GAAG,eAAe;IACzE,kBAAkB,CACvB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,GAAG,eAAe,GACrC,IAAI;IACA,mBAAmB,CACxB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,SAAS,GAAG,eAAe,GACrC,eAAe;CAGnB"} \ No newline at end of file diff --git a/packages/expo-sqlite/build/NativeSession.js.map b/packages/expo-sqlite/build/NativeSession.js.map index dcbac9932d64e9..1889d1fab9cf4b 100644 --- a/packages/expo-sqlite/build/NativeSession.js.map +++ b/packages/expo-sqlite/build/NativeSession.js.map @@ -1 +1 @@ -{"version":3,"file":"NativeSession.js","sourceRoot":"","sources":["../src/NativeSession.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * A type that represents a changeset.\n */\nexport type Changeset = Uint8Array;\n\nexport type SQLiteAnyDatabase = any;\n\nexport declare class NativeSession {\n //#region Asynchronous API\n\n public attachAsync(database: SQLiteAnyDatabase, table: string | null): Promise;\n public enableAsync(database: SQLiteAnyDatabase, enabled: boolean): Promise;\n public closeAsync(database: SQLiteAnyDatabase): Promise;\n\n public createChangesetAsync(database: SQLiteAnyDatabase): Promise;\n public createInvertedChangesetAsync(database: SQLiteAnyDatabase): Promise;\n public applyChangesetAsync(database: SQLiteAnyDatabase, changeset: Changeset): Promise;\n public invertChangesetAsync(\n database: SQLiteAnyDatabase,\n changeset: Changeset\n ): Promise;\n\n //#endregion\n\n //#region Synchronous API\n\n public attachSync(database: SQLiteAnyDatabase, table: string | null): void;\n public enableSync(database: SQLiteAnyDatabase, enabled: boolean): void;\n public closeSync(database: SQLiteAnyDatabase): void;\n\n public createChangesetSync(database: SQLiteAnyDatabase): Changeset;\n public createInvertedChangesetSync(database: SQLiteAnyDatabase): Changeset;\n public applyChangesetSync(database: SQLiteAnyDatabase, changeset: Changeset): void;\n public invertChangesetSync(database: SQLiteAnyDatabase, changeset: Changeset): Changeset;\n\n //#endregion\n}\n"]} \ No newline at end of file +{"version":3,"file":"NativeSession.js","sourceRoot":"","sources":["../src/NativeSession.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * A type that represents a changeset.\n */\nexport type Changeset = Uint8Array;\nexport type NativeChangeset = ArrayBuffer;\n\nexport type SQLiteAnyDatabase = any;\n\nexport declare class NativeSession {\n //#region Asynchronous API\n\n public attachAsync(database: SQLiteAnyDatabase, table: string | null): Promise;\n public enableAsync(database: SQLiteAnyDatabase, enabled: boolean): Promise;\n public closeAsync(database: SQLiteAnyDatabase): Promise;\n\n public createChangesetAsync(database: SQLiteAnyDatabase): Promise;\n public createInvertedChangesetAsync(database: SQLiteAnyDatabase): Promise;\n public applyChangesetAsync(\n database: SQLiteAnyDatabase,\n changeset: Changeset | NativeChangeset\n ): Promise;\n public invertChangesetAsync(\n database: SQLiteAnyDatabase,\n changeset: Changeset | NativeChangeset\n ): Promise;\n\n //#endregion\n\n //#region Synchronous API\n\n public attachSync(database: SQLiteAnyDatabase, table: string | null): void;\n public enableSync(database: SQLiteAnyDatabase, enabled: boolean): void;\n public closeSync(database: SQLiteAnyDatabase): void;\n\n public createChangesetSync(database: SQLiteAnyDatabase): NativeChangeset;\n public createInvertedChangesetSync(database: SQLiteAnyDatabase): NativeChangeset;\n public applyChangesetSync(\n database: SQLiteAnyDatabase,\n changeset: Changeset | NativeChangeset\n ): void;\n public invertChangesetSync(\n database: SQLiteAnyDatabase,\n changeset: Changeset | NativeChangeset\n ): NativeChangeset;\n\n //#endregion\n}\n"]} \ No newline at end of file diff --git a/packages/expo-sqlite/build/SQLiteSession.d.ts.map b/packages/expo-sqlite/build/SQLiteSession.d.ts.map index d12b1120b2ce12..09d9882eef8284 100644 --- a/packages/expo-sqlite/build/SQLiteSession.d.ts.map +++ b/packages/expo-sqlite/build/SQLiteSession.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"SQLiteSession.d.ts","sourceRoot":"","sources":["../src/SQLiteSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEhE,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC;AAE1B;;;GAGG;AACH,qBAAa,aAAa;IAEtB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa;gBADb,cAAc,EAAE,cAAc,EAC9B,aAAa,EAAE,aAAa;IAK/C;;;;OAIG;IACI,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD;;;;OAIG;IACI,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD;;;OAGG;IACI,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIlC;;;OAGG;IACI,oBAAoB,IAAI,OAAO,CAAC,SAAS,CAAC;IAIjD;;;OAGG;IACI,4BAA4B,IAAI,OAAO,CAAC,SAAS,CAAC;IAIzD;;;;OAIG;IACI,mBAAmB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/D;;;;OAIG;IACI,oBAAoB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAQrE;;;;;;;OAOG;IACI,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7C;;;;;;;OAOG;IACI,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIzC;;;;;;OAMG;IACI,SAAS,IAAI,IAAI;IAIxB;;;;;;OAMG;IACI,mBAAmB,IAAI,SAAS;IAIvC;;;;;OAKG;IACI,2BAA2B,IAAI,SAAS;IAI/C;;;;;;;OAOG;IACI,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAIrD;;;;;;;OAOG;IACI,mBAAmB,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS;CAK5D"} \ No newline at end of file +{"version":3,"file":"SQLiteSession.d.ts","sourceRoot":"","sources":["../src/SQLiteSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEhE,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC;AAE1B;;;GAGG;AACH,qBAAa,aAAa;IAEtB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa;gBADb,cAAc,EAAE,cAAc,EAC9B,aAAa,EAAE,aAAa;IAK/C;;;;OAIG;IACI,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD;;;;OAIG;IACI,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD;;;OAGG;IACI,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIlC;;;OAGG;IACU,oBAAoB,IAAI,OAAO,CAAC,SAAS,CAAC;IAKvD;;;OAGG;IACU,4BAA4B,IAAI,OAAO,CAAC,SAAS,CAAC;IAO/D;;;;OAIG;IACI,mBAAmB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/D;;;;OAIG;IACU,oBAAoB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAY3E;;;;;;;OAOG;IACI,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7C;;;;;;;OAOG;IACI,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIzC;;;;;;OAMG;IACI,SAAS,IAAI,IAAI;IAIxB;;;;;;OAMG;IACI,mBAAmB,IAAI,SAAS;IAKvC;;;;;OAKG;IACI,2BAA2B,IAAI,SAAS;IAK/C;;;;;;;OAOG;IACI,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAIrD;;;;;;;OAOG;IACI,mBAAmB,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS;CAM5D"} \ No newline at end of file diff --git a/packages/expo-sqlite/build/SQLiteSession.js b/packages/expo-sqlite/build/SQLiteSession.js index 6f80240df3675d..fef2bd41518fbd 100644 --- a/packages/expo-sqlite/build/SQLiteSession.js +++ b/packages/expo-sqlite/build/SQLiteSession.js @@ -37,15 +37,17 @@ export class SQLiteSession { * Create a changeset asynchronously. * @see [`sqlite3session_changeset`](https://www.sqlite.org/session/sqlite3session_changeset.html) */ - createChangesetAsync() { - return this.nativeSession.createChangesetAsync(this.nativeDatabase); + async createChangesetAsync() { + const changesetBuffer = await this.nativeSession.createChangesetAsync(this.nativeDatabase); + return new Uint8Array(changesetBuffer); } /** * Create an inverted changeset asynchronously. * This is a shorthand for [`createChangesetAsync()`](#createchangesetasync) + [`invertChangesetAsync()`](#invertchangesetasyncchangeset). */ - createInvertedChangesetAsync() { - return this.nativeSession.createInvertedChangesetAsync(this.nativeDatabase); + async createInvertedChangesetAsync() { + const changesetBuffer = await this.nativeSession.createInvertedChangesetAsync(this.nativeDatabase); + return new Uint8Array(changesetBuffer); } /** * Apply a changeset asynchronously. @@ -60,8 +62,9 @@ export class SQLiteSession { * @see [`sqlite3changeset_invert`](https://www.sqlite.org/session/sqlite3changeset_invert.html) * @param changeset The changeset to invert. */ - invertChangesetAsync(changeset) { - return this.nativeSession.invertChangesetAsync(this.nativeDatabase, changeset); + async invertChangesetAsync(changeset) { + const changesetBuffer = await this.nativeSession.invertChangesetAsync(this.nativeDatabase, changeset); + return new Uint8Array(changesetBuffer); } //#endregion //#region Synchronous API @@ -105,7 +108,8 @@ export class SQLiteSession { * @see [`sqlite3session_changeset`](https://www.sqlite.org/session/sqlite3session_changeset.html) */ createChangesetSync() { - return this.nativeSession.createChangesetSync(this.nativeDatabase); + const changesetBuffer = this.nativeSession.createChangesetSync(this.nativeDatabase); + return new Uint8Array(changesetBuffer); } /** * Create an inverted changeset synchronously. @@ -114,7 +118,8 @@ export class SQLiteSession { * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance. */ createInvertedChangesetSync() { - return this.nativeSession.createInvertedChangesetSync(this.nativeDatabase); + const changesetBuffer = this.nativeSession.createInvertedChangesetSync(this.nativeDatabase); + return new Uint8Array(changesetBuffer); } /** * Apply a changeset synchronously. @@ -136,7 +141,8 @@ export class SQLiteSession { * @see [`sqlite3changeset_invert`](https://www.sqlite.org/session/sqlite3changeset_invert.html) */ invertChangesetSync(changeset) { - return this.nativeSession.invertChangesetSync(this.nativeDatabase, changeset); + const changesetBuffer = this.nativeSession.invertChangesetSync(this.nativeDatabase, changeset); + return new Uint8Array(changesetBuffer); } } //# sourceMappingURL=SQLiteSession.js.map \ No newline at end of file diff --git a/packages/expo-sqlite/build/SQLiteSession.js.map b/packages/expo-sqlite/build/SQLiteSession.js.map index 1e8bcd9e2cba40..a3944f5010d990 100644 --- a/packages/expo-sqlite/build/SQLiteSession.js.map +++ b/packages/expo-sqlite/build/SQLiteSession.js.map @@ -1 +1 @@ -{"version":3,"file":"SQLiteSession.js","sourceRoot":"","sources":["../src/SQLiteSession.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,MAAM,OAAO,aAAa;IAEL;IACA;IAFnB,YACmB,cAA8B,EAC9B,aAA4B;QAD5B,mBAAc,GAAd,cAAc,CAAgB;QAC9B,kBAAa,GAAb,aAAa,CAAe;IAC5C,CAAC;IAEJ,0BAA0B;IAE1B;;;;OAIG;IACI,WAAW,CAAC,KAAoB;QACrC,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACpE,CAAC;IAED;;;;OAIG;IACI,WAAW,CAAC,OAAgB;QACjC,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACtE,CAAC;IAED;;;OAGG;IACI,UAAU;QACf,OAAO,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC5D,CAAC;IAED;;;OAGG;IACI,oBAAoB;QACzB,OAAO,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACtE,CAAC;IAED;;;OAGG;IACI,4BAA4B;QACjC,OAAO,IAAI,CAAC,aAAa,CAAC,4BAA4B,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC9E,CAAC;IAED;;;;OAIG;IACI,mBAAmB,CAAC,SAAoB;QAC7C,OAAO,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IAChF,CAAC;IAED;;;;OAIG;IACI,oBAAoB,CAAC,SAAoB;QAC9C,OAAO,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IACjF,CAAC;IAED,YAAY;IAEZ,yBAAyB;IAEzB;;;;;;;OAOG;IACI,UAAU,CAAC,KAAoB;QACpC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IAC5D,CAAC;IAED;;;;;;;OAOG;IACI,UAAU,CAAC,OAAgB;QAChC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IAC9D,CAAC;IAED;;;;;;OAMG;IACI,SAAS;QACd,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACpD,CAAC;IAED;;;;;;OAMG;IACI,mBAAmB;QACxB,OAAO,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACrE,CAAC;IAED;;;;;OAKG;IACI,2BAA2B;QAChC,OAAO,IAAI,CAAC,aAAa,CAAC,2BAA2B,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7E,CAAC;IAED;;;;;;;OAOG;IACI,kBAAkB,CAAC,SAAoB;QAC5C,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IACxE,CAAC;IAED;;;;;;;OAOG;IACI,mBAAmB,CAAC,SAAoB;QAC7C,OAAO,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IAChF,CAAC;CAGF","sourcesContent":["import { type NativeDatabase } from './NativeDatabase';\nimport { NativeSession, type Changeset } from './NativeSession';\n\nexport { type Changeset };\n\n/**\n * A class that represents an instance of the SQLite session extension.\n * @see [Session Extension](https://www.sqlite.org/sessionintro.html)\n */\nexport class SQLiteSession {\n constructor(\n private readonly nativeDatabase: NativeDatabase,\n private readonly nativeSession: NativeSession\n ) {}\n\n //#region Asynchronous API\n\n /**\n * Attach a table to the session asynchronously.\n * @see [`sqlite3session_attach`](https://www.sqlite.org/session/sqlite3session_attach.html)\n * @param table The table to attach. If `null`, all tables are attached.\n */\n public attachAsync(table: string | null): Promise {\n return this.nativeSession.attachAsync(this.nativeDatabase, table);\n }\n\n /**\n * Enable or disable the session asynchronously.\n * @see [`sqlite3session_enable`](https://www.sqlite.org/session/sqlite3session_enable.html)\n * @param enabled Whether to enable or disable the session.\n */\n public enableAsync(enabled: boolean): Promise {\n return this.nativeSession.enableAsync(this.nativeDatabase, enabled);\n }\n\n /**\n * Close the session asynchronously.\n * @see [`sqlite3session_delete`](https://www.sqlite.org/session/sqlite3session_delete.html)\n */\n public closeAsync(): Promise {\n return this.nativeSession.closeAsync(this.nativeDatabase);\n }\n\n /**\n * Create a changeset asynchronously.\n * @see [`sqlite3session_changeset`](https://www.sqlite.org/session/sqlite3session_changeset.html)\n */\n public createChangesetAsync(): Promise {\n return this.nativeSession.createChangesetAsync(this.nativeDatabase);\n }\n\n /**\n * Create an inverted changeset asynchronously.\n * This is a shorthand for [`createChangesetAsync()`](#createchangesetasync) + [`invertChangesetAsync()`](#invertchangesetasyncchangeset).\n */\n public createInvertedChangesetAsync(): Promise {\n return this.nativeSession.createInvertedChangesetAsync(this.nativeDatabase);\n }\n\n /**\n * Apply a changeset asynchronously.\n * @see [`sqlite3changeset_apply`](https://www.sqlite.org/session/sqlite3changeset_apply.html)\n * @param changeset The changeset to apply.\n */\n public applyChangesetAsync(changeset: Changeset): Promise {\n return this.nativeSession.applyChangesetAsync(this.nativeDatabase, changeset);\n }\n\n /**\n * Invert a changeset asynchronously.\n * @see [`sqlite3changeset_invert`](https://www.sqlite.org/session/sqlite3changeset_invert.html)\n * @param changeset The changeset to invert.\n */\n public invertChangesetAsync(changeset: Changeset): Promise {\n return this.nativeSession.invertChangesetAsync(this.nativeDatabase, changeset);\n }\n\n //#endregion\n\n //#region Synchronous API\n\n /**\n * Attach a table to the session synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @param table The table to attach.\n * @see [`sqlite3session_attach`](https://www.sqlite.org/session/sqlite3session_attach.html)\n */\n public attachSync(table: string | null): void {\n this.nativeSession.attachSync(this.nativeDatabase, table);\n }\n\n /**\n * Enable or disable the session synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @param enabled Whether to enable or disable the session.\n * @see [`sqlite3session_enable`](https://www.sqlite.org/session/sqlite3session_enable.html)\n */\n public enableSync(enabled: boolean): void {\n this.nativeSession.enableSync(this.nativeDatabase, enabled);\n }\n\n /**\n * Close the session synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @see [`sqlite3session_delete`](https://www.sqlite.org/session/sqlite3session_delete.html)\n */\n public closeSync(): void {\n this.nativeSession.closeSync(this.nativeDatabase);\n }\n\n /**\n * Create a changeset synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @see [`sqlite3session_changeset`](https://www.sqlite.org/session/sqlite3session_changeset.html)\n */\n public createChangesetSync(): Changeset {\n return this.nativeSession.createChangesetSync(this.nativeDatabase);\n }\n\n /**\n * Create an inverted changeset synchronously.\n * This is a shorthand for [`createChangesetSync()`](#createchangesetsync) + [`invertChangesetSync()`](#invertchangesetsyncchangeset).\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n */\n public createInvertedChangesetSync(): Changeset {\n return this.nativeSession.createInvertedChangesetSync(this.nativeDatabase);\n }\n\n /**\n * Apply a changeset synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @param changeset The changeset to apply.\n * @see [`sqlite3changeset_apply`](https://www.sqlite.org/session/sqlite3changeset_apply.html)\n */\n public applyChangesetSync(changeset: Changeset): void {\n this.nativeSession.applyChangesetSync(this.nativeDatabase, changeset);\n }\n\n /**\n * Invert a changeset synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @param changeset The changeset to invert.\n * @see [`sqlite3changeset_invert`](https://www.sqlite.org/session/sqlite3changeset_invert.html)\n */\n public invertChangesetSync(changeset: Changeset): Changeset {\n return this.nativeSession.invertChangesetSync(this.nativeDatabase, changeset);\n }\n\n //#endregion\n}\n"]} \ No newline at end of file +{"version":3,"file":"SQLiteSession.js","sourceRoot":"","sources":["../src/SQLiteSession.ts"],"names":[],"mappings":"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","sourcesContent":["import { type NativeDatabase } from './NativeDatabase';\nimport { NativeSession, type Changeset } from './NativeSession';\n\nexport { type Changeset };\n\n/**\n * A class that represents an instance of the SQLite session extension.\n * @see [Session Extension](https://www.sqlite.org/sessionintro.html)\n */\nexport class SQLiteSession {\n constructor(\n private readonly nativeDatabase: NativeDatabase,\n private readonly nativeSession: NativeSession\n ) {}\n\n //#region Asynchronous API\n\n /**\n * Attach a table to the session asynchronously.\n * @see [`sqlite3session_attach`](https://www.sqlite.org/session/sqlite3session_attach.html)\n * @param table The table to attach. If `null`, all tables are attached.\n */\n public attachAsync(table: string | null): Promise {\n return this.nativeSession.attachAsync(this.nativeDatabase, table);\n }\n\n /**\n * Enable or disable the session asynchronously.\n * @see [`sqlite3session_enable`](https://www.sqlite.org/session/sqlite3session_enable.html)\n * @param enabled Whether to enable or disable the session.\n */\n public enableAsync(enabled: boolean): Promise {\n return this.nativeSession.enableAsync(this.nativeDatabase, enabled);\n }\n\n /**\n * Close the session asynchronously.\n * @see [`sqlite3session_delete`](https://www.sqlite.org/session/sqlite3session_delete.html)\n */\n public closeAsync(): Promise {\n return this.nativeSession.closeAsync(this.nativeDatabase);\n }\n\n /**\n * Create a changeset asynchronously.\n * @see [`sqlite3session_changeset`](https://www.sqlite.org/session/sqlite3session_changeset.html)\n */\n public async createChangesetAsync(): Promise {\n const changesetBuffer = await this.nativeSession.createChangesetAsync(this.nativeDatabase);\n return new Uint8Array(changesetBuffer);\n }\n\n /**\n * Create an inverted changeset asynchronously.\n * This is a shorthand for [`createChangesetAsync()`](#createchangesetasync) + [`invertChangesetAsync()`](#invertchangesetasyncchangeset).\n */\n public async createInvertedChangesetAsync(): Promise {\n const changesetBuffer = await this.nativeSession.createInvertedChangesetAsync(\n this.nativeDatabase\n );\n return new Uint8Array(changesetBuffer);\n }\n\n /**\n * Apply a changeset asynchronously.\n * @see [`sqlite3changeset_apply`](https://www.sqlite.org/session/sqlite3changeset_apply.html)\n * @param changeset The changeset to apply.\n */\n public applyChangesetAsync(changeset: Changeset): Promise {\n return this.nativeSession.applyChangesetAsync(this.nativeDatabase, changeset);\n }\n\n /**\n * Invert a changeset asynchronously.\n * @see [`sqlite3changeset_invert`](https://www.sqlite.org/session/sqlite3changeset_invert.html)\n * @param changeset The changeset to invert.\n */\n public async invertChangesetAsync(changeset: Changeset): Promise {\n const changesetBuffer = await this.nativeSession.invertChangesetAsync(\n this.nativeDatabase,\n changeset\n );\n return new Uint8Array(changesetBuffer);\n }\n\n //#endregion\n\n //#region Synchronous API\n\n /**\n * Attach a table to the session synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @param table The table to attach.\n * @see [`sqlite3session_attach`](https://www.sqlite.org/session/sqlite3session_attach.html)\n */\n public attachSync(table: string | null): void {\n this.nativeSession.attachSync(this.nativeDatabase, table);\n }\n\n /**\n * Enable or disable the session synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @param enabled Whether to enable or disable the session.\n * @see [`sqlite3session_enable`](https://www.sqlite.org/session/sqlite3session_enable.html)\n */\n public enableSync(enabled: boolean): void {\n this.nativeSession.enableSync(this.nativeDatabase, enabled);\n }\n\n /**\n * Close the session synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @see [`sqlite3session_delete`](https://www.sqlite.org/session/sqlite3session_delete.html)\n */\n public closeSync(): void {\n this.nativeSession.closeSync(this.nativeDatabase);\n }\n\n /**\n * Create a changeset synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @see [`sqlite3session_changeset`](https://www.sqlite.org/session/sqlite3session_changeset.html)\n */\n public createChangesetSync(): Changeset {\n const changesetBuffer = this.nativeSession.createChangesetSync(this.nativeDatabase);\n return new Uint8Array(changesetBuffer);\n }\n\n /**\n * Create an inverted changeset synchronously.\n * This is a shorthand for [`createChangesetSync()`](#createchangesetsync) + [`invertChangesetSync()`](#invertchangesetsyncchangeset).\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n */\n public createInvertedChangesetSync(): Changeset {\n const changesetBuffer = this.nativeSession.createInvertedChangesetSync(this.nativeDatabase);\n return new Uint8Array(changesetBuffer);\n }\n\n /**\n * Apply a changeset synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @param changeset The changeset to apply.\n * @see [`sqlite3changeset_apply`](https://www.sqlite.org/session/sqlite3changeset_apply.html)\n */\n public applyChangesetSync(changeset: Changeset): void {\n this.nativeSession.applyChangesetSync(this.nativeDatabase, changeset);\n }\n\n /**\n * Invert a changeset synchronously.\n *\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n *\n * @param changeset The changeset to invert.\n * @see [`sqlite3changeset_invert`](https://www.sqlite.org/session/sqlite3changeset_invert.html)\n */\n public invertChangesetSync(changeset: Changeset): Changeset {\n const changesetBuffer = this.nativeSession.invertChangesetSync(this.nativeDatabase, changeset);\n return new Uint8Array(changesetBuffer);\n }\n\n //#endregion\n}\n"]} \ No newline at end of file diff --git a/packages/expo-sqlite/ios/SQLiteModule.swift b/packages/expo-sqlite/ios/SQLiteModule.swift index 15c784fb31629b..f1c32d9602a1e7 100644 --- a/packages/expo-sqlite/ios/SQLiteModule.swift +++ b/packages/expo-sqlite/ios/SQLiteModule.swift @@ -270,31 +270,31 @@ public final class SQLiteModule: Module { try sessionClose(database: database, session: session) } - AsyncFunction("createChangesetAsync") { (session: NativeSession, database: NativeDatabase) -> Data in + AsyncFunction("createChangesetAsync") { (session: NativeSession, database: NativeDatabase) -> NativeArrayBuffer in return try sessionCreateChangeset(database: database, session: session) }.runOnQueue(moduleQueue) - Function("createChangesetSync") { (session: NativeSession, database: NativeDatabase) -> Data in + Function("createChangesetSync") { (session: NativeSession, database: NativeDatabase) -> NativeArrayBuffer in return try sessionCreateChangeset(database: database, session: session) } - AsyncFunction("createInvertedChangesetAsync") { (session: NativeSession, database: NativeDatabase) -> Data in + AsyncFunction("createInvertedChangesetAsync") { (session: NativeSession, database: NativeDatabase) -> NativeArrayBuffer in return try sessionCreateInvertedChangeset(database: database, session: session) }.runOnQueue(moduleQueue) - Function("createInvertedChangesetSync") { (session: NativeSession, database: NativeDatabase) -> Data in + Function("createInvertedChangesetSync") { (session: NativeSession, database: NativeDatabase) -> NativeArrayBuffer in return try sessionCreateInvertedChangeset(database: database, session: session) } - AsyncFunction("applyChangesetAsync") { (session: NativeSession, database: NativeDatabase, changeset: Data) in + AsyncFunction("applyChangesetAsync") { (session: NativeSession, database: NativeDatabase, changeset: NativeArrayBuffer) in try sessionApplyChangeset(database: database, session: session, changeset: changeset) }.runOnQueue(moduleQueue) - Function("applyChangesetSync") { (session: NativeSession, database: NativeDatabase, changeset: Data) in + Function("applyChangesetSync") { (session: NativeSession, database: NativeDatabase, changeset: JavaScriptArrayBuffer) in try sessionApplyChangeset(database: database, session: session, changeset: changeset) } - AsyncFunction("invertChangesetAsync") { (session: NativeSession, database: NativeDatabase, changeset: Data) -> Data in + AsyncFunction("invertChangesetAsync") { (session: NativeSession, database: NativeDatabase, changeset: NativeArrayBuffer) -> NativeArrayBuffer in return try sessionInvertChangeset(database: database, session: session, changeset: changeset) }.runOnQueue(moduleQueue) - Function("invertChangesetSync") { (session: NativeSession, database: NativeDatabase, changeset: Data) -> Data in + Function("invertChangesetSync") { (session: NativeSession, database: NativeDatabase, changeset: JavaScriptArrayBuffer) -> NativeArrayBuffer in return try sessionInvertChangeset(database: database, session: session, changeset: changeset) } } @@ -762,7 +762,7 @@ public final class SQLiteModule: Module { exsqlite3session_delete(session.pointer) } - private func sessionCreateChangeset(database: NativeDatabase, session: NativeSession) throws -> Data { + private func sessionCreateChangeset(database: NativeDatabase, session: NativeSession) throws -> NativeArrayBuffer { try maybeThrowForClosedDatabase(database) var size: Int32 = 0 var buffer: UnsafeMutableRawPointer? @@ -770,13 +770,13 @@ public final class SQLiteModule: Module { throw SQLiteErrorException(convertSqlLiteErrorToString(database)) } guard let buffer else { - return Data() + return NativeArrayBuffer.allocate(size: 0) } defer { exsqlite3_free(buffer) } - return Data(bytes: buffer, count: Int(size)) + return NativeArrayBuffer.copy(of: buffer, count: Int(size)) } - private func sessionCreateInvertedChangeset(database: NativeDatabase, session: NativeSession) throws -> Data { + private func sessionCreateInvertedChangeset(database: NativeDatabase, session: NativeSession) throws -> NativeArrayBuffer { do { let changeset = try sessionCreateChangeset(database: database, session: session) return try sessionInvertChangeset(database: database, session: session, changeset: changeset) @@ -785,13 +785,13 @@ public final class SQLiteModule: Module { } } - private func sessionApplyChangeset(database: NativeDatabase, session: NativeSession, changeset: Data) throws { + private func sessionApplyChangeset(database: NativeDatabase, session: NativeSession, changeset: ArrayBuffer) throws { try maybeThrowForClosedDatabase(database) try changeset.withUnsafeBytes { let buffer = UnsafeMutableRawPointer(mutating: $0.baseAddress) if exsqlite3changeset_apply( database.pointer, - Int32(changeset.count), + Int32(changeset.byteLength), buffer, nil, { _, _, _ -> Int32 in @@ -804,21 +804,21 @@ public final class SQLiteModule: Module { } } - private func sessionInvertChangeset(database: NativeDatabase, session: NativeSession, changeset: Data) throws -> Data { + private func sessionInvertChangeset(database: NativeDatabase, session: NativeSession, changeset: ArrayBuffer) throws -> NativeArrayBuffer { try maybeThrowForClosedDatabase(database) return try changeset.withUnsafeBytes { let inBuffer = UnsafeMutableRawPointer(mutating: $0.baseAddress) var outSize: Int32 = 0 var outBuffer: UnsafeMutableRawPointer? - if exsqlite3changeset_invert(Int32(changeset.count), inBuffer, &outSize, &outBuffer) != SQLITE_OK { + if exsqlite3changeset_invert(Int32(changeset.byteLength), inBuffer, &outSize, &outBuffer) != SQLITE_OK { throw SQLiteErrorException(convertSqlLiteErrorToString(database)) } guard let outBuffer else { - return Data() + return NativeArrayBuffer.allocate(size: 0) } defer { exsqlite3_free(outBuffer) } - return Data(bytes: outBuffer, count: Int(outSize)) + return NativeArrayBuffer.copy(of: outBuffer, count: Int(outSize)) } } } diff --git a/packages/expo-sqlite/src/NativeSession.ts b/packages/expo-sqlite/src/NativeSession.ts index a3b4bec8061bbb..3cacce43a3a911 100644 --- a/packages/expo-sqlite/src/NativeSession.ts +++ b/packages/expo-sqlite/src/NativeSession.ts @@ -2,6 +2,7 @@ * A type that represents a changeset. */ export type Changeset = Uint8Array; +export type NativeChangeset = ArrayBuffer; export type SQLiteAnyDatabase = any; @@ -12,13 +13,16 @@ export declare class NativeSession { public enableAsync(database: SQLiteAnyDatabase, enabled: boolean): Promise; public closeAsync(database: SQLiteAnyDatabase): Promise; - public createChangesetAsync(database: SQLiteAnyDatabase): Promise; - public createInvertedChangesetAsync(database: SQLiteAnyDatabase): Promise; - public applyChangesetAsync(database: SQLiteAnyDatabase, changeset: Changeset): Promise; + public createChangesetAsync(database: SQLiteAnyDatabase): Promise; + public createInvertedChangesetAsync(database: SQLiteAnyDatabase): Promise; + public applyChangesetAsync( + database: SQLiteAnyDatabase, + changeset: Changeset | NativeChangeset + ): Promise; public invertChangesetAsync( database: SQLiteAnyDatabase, - changeset: Changeset - ): Promise; + changeset: Changeset | NativeChangeset + ): Promise; //#endregion @@ -28,10 +32,16 @@ export declare class NativeSession { public enableSync(database: SQLiteAnyDatabase, enabled: boolean): void; public closeSync(database: SQLiteAnyDatabase): void; - public createChangesetSync(database: SQLiteAnyDatabase): Changeset; - public createInvertedChangesetSync(database: SQLiteAnyDatabase): Changeset; - public applyChangesetSync(database: SQLiteAnyDatabase, changeset: Changeset): void; - public invertChangesetSync(database: SQLiteAnyDatabase, changeset: Changeset): Changeset; + public createChangesetSync(database: SQLiteAnyDatabase): NativeChangeset; + public createInvertedChangesetSync(database: SQLiteAnyDatabase): NativeChangeset; + public applyChangesetSync( + database: SQLiteAnyDatabase, + changeset: Changeset | NativeChangeset + ): void; + public invertChangesetSync( + database: SQLiteAnyDatabase, + changeset: Changeset | NativeChangeset + ): NativeChangeset; //#endregion } diff --git a/packages/expo-sqlite/src/SQLiteSession.ts b/packages/expo-sqlite/src/SQLiteSession.ts index 36fe0226e0b100..5a37673951cdfe 100644 --- a/packages/expo-sqlite/src/SQLiteSession.ts +++ b/packages/expo-sqlite/src/SQLiteSession.ts @@ -45,16 +45,20 @@ export class SQLiteSession { * Create a changeset asynchronously. * @see [`sqlite3session_changeset`](https://www.sqlite.org/session/sqlite3session_changeset.html) */ - public createChangesetAsync(): Promise { - return this.nativeSession.createChangesetAsync(this.nativeDatabase); + public async createChangesetAsync(): Promise { + const changesetBuffer = await this.nativeSession.createChangesetAsync(this.nativeDatabase); + return new Uint8Array(changesetBuffer); } /** * Create an inverted changeset asynchronously. * This is a shorthand for [`createChangesetAsync()`](#createchangesetasync) + [`invertChangesetAsync()`](#invertchangesetasyncchangeset). */ - public createInvertedChangesetAsync(): Promise { - return this.nativeSession.createInvertedChangesetAsync(this.nativeDatabase); + public async createInvertedChangesetAsync(): Promise { + const changesetBuffer = await this.nativeSession.createInvertedChangesetAsync( + this.nativeDatabase + ); + return new Uint8Array(changesetBuffer); } /** @@ -71,8 +75,12 @@ export class SQLiteSession { * @see [`sqlite3changeset_invert`](https://www.sqlite.org/session/sqlite3changeset_invert.html) * @param changeset The changeset to invert. */ - public invertChangesetAsync(changeset: Changeset): Promise { - return this.nativeSession.invertChangesetAsync(this.nativeDatabase, changeset); + public async invertChangesetAsync(changeset: Changeset): Promise { + const changesetBuffer = await this.nativeSession.invertChangesetAsync( + this.nativeDatabase, + changeset + ); + return new Uint8Array(changesetBuffer); } //#endregion @@ -122,7 +130,8 @@ export class SQLiteSession { * @see [`sqlite3session_changeset`](https://www.sqlite.org/session/sqlite3session_changeset.html) */ public createChangesetSync(): Changeset { - return this.nativeSession.createChangesetSync(this.nativeDatabase); + const changesetBuffer = this.nativeSession.createChangesetSync(this.nativeDatabase); + return new Uint8Array(changesetBuffer); } /** @@ -132,7 +141,8 @@ export class SQLiteSession { * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance. */ public createInvertedChangesetSync(): Changeset { - return this.nativeSession.createInvertedChangesetSync(this.nativeDatabase); + const changesetBuffer = this.nativeSession.createInvertedChangesetSync(this.nativeDatabase); + return new Uint8Array(changesetBuffer); } /** @@ -156,7 +166,8 @@ export class SQLiteSession { * @see [`sqlite3changeset_invert`](https://www.sqlite.org/session/sqlite3changeset_invert.html) */ public invertChangesetSync(changeset: Changeset): Changeset { - return this.nativeSession.invertChangesetSync(this.nativeDatabase, changeset); + const changesetBuffer = this.nativeSession.invertChangesetSync(this.nativeDatabase, changeset); + return new Uint8Array(changesetBuffer); } //#endregion diff --git a/packages/expo-sqlite/web/SQLiteModule.node.ts b/packages/expo-sqlite/web/SQLiteModule.node.ts index cdaf0b4fd0d219..5e4234afe894cc 100644 --- a/packages/expo-sqlite/web/SQLiteModule.node.ts +++ b/packages/expo-sqlite/web/SQLiteModule.node.ts @@ -3,7 +3,7 @@ import { registerWebModule, NativeModule } from 'expo'; import { type SQLiteOpenOptions } from '../src/NativeDatabase'; -import { type Changeset } from '../src/NativeSession'; +import { type Changeset, type NativeChangeset } from '../src/NativeSession'; import { type SQLiteBindBlobParams, type SQLiteBindPrimitiveParams, @@ -120,28 +120,28 @@ export class NativeSession { async closeAsync(database: NativeDatabase): Promise {} closeSync(database: NativeDatabase): void {} - async createChangesetAsync(database: NativeDatabase): Promise { - return new Uint8Array(); + async createChangesetAsync(database: NativeDatabase): Promise { + return new Uint8Array().buffer; } - createChangesetSync(database: NativeDatabase): Changeset { - return new Uint8Array(); + createChangesetSync(database: NativeDatabase): NativeChangeset { + return new Uint8Array().buffer; } - async createInvertedChangesetAsync(database: NativeDatabase): Promise { - return new Uint8Array(); + async createInvertedChangesetAsync(database: NativeDatabase): Promise { + return new Uint8Array().buffer; } - createInvertedChangesetSync(database: NativeDatabase): Changeset { - return new Uint8Array(); + createInvertedChangesetSync(database: NativeDatabase): NativeChangeset { + return new Uint8Array().buffer; } - async applyChangesetAsync(database: NativeDatabase, changeset: Changeset): Promise {} - applyChangesetSync(database: NativeDatabase, changeset: Changeset): void {} + async applyChangesetAsync(database: NativeDatabase, changeset: Changeset | NativeChangeset): Promise {} + applyChangesetSync(database: NativeDatabase, changeset: Changeset | NativeChangeset): void {} - async invertChangesetAsync(database: NativeDatabase, changeset: Changeset): Promise { - return new Uint8Array(); + async invertChangesetAsync(database: NativeDatabase, changeset: Changeset | NativeChangeset): Promise { + return new Uint8Array().buffer; } - invertChangesetSync(database: NativeDatabase, changeset: Changeset): Changeset { - return new Uint8Array(); + invertChangesetSync(database: NativeDatabase, changeset: Changeset | NativeChangeset): NativeChangeset { + return new Uint8Array().buffer; } } diff --git a/packages/expo-sqlite/web/SQLiteModule.ts b/packages/expo-sqlite/web/SQLiteModule.ts index 717c987d2d629c..a25679db42bf8b 100644 --- a/packages/expo-sqlite/web/SQLiteModule.ts +++ b/packages/expo-sqlite/web/SQLiteModule.ts @@ -4,7 +4,7 @@ import { registerWebModule, NativeModule } from 'expo'; import { invokeWorkerAsync, invokeWorkerSync, workerMessageHandler } from './WorkerChannel'; import { type SQLiteOpenOptions } from '../src/NativeDatabase'; -import { type Changeset } from '../src/NativeSession'; +import { type Changeset, type NativeChangeset } from '../src/NativeSession'; import { type SQLiteBindBlobParams, type SQLiteBindPrimitiveParams, @@ -33,6 +33,11 @@ function getWorker(): Worker { return worker; } +// web worker works on Uint8Array, but session methods accept ArrayBuffer too +function convertChangesetInput(changeset: Changeset | NativeChangeset): Changeset { + return ArrayBuffer.isView(changeset) ? changeset : new Uint8Array(changeset); +} + class NativeDatabase { public readonly id: number; @@ -331,60 +336,66 @@ export class NativeSession { }); } - async createChangesetAsync(database: NativeDatabase): Promise { - return await invokeWorkerAsync(getWorker(), 'sessionCreateChangeset', { + async createChangesetAsync(database: NativeDatabase): Promise { + const result = await invokeWorkerAsync(getWorker(), 'sessionCreateChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); + return result.buffer as ArrayBuffer; } - createChangesetSync(database: NativeDatabase): Changeset { - return invokeWorkerSync(getWorker(), 'sessionCreateChangeset', { + createChangesetSync(database: NativeDatabase): NativeChangeset { + const result = invokeWorkerSync(getWorker(), 'sessionCreateChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); + return result.buffer as ArrayBuffer; } - async createInvertedChangesetAsync(database: NativeDatabase): Promise { - return await invokeWorkerAsync(getWorker(), 'sessionCreateInvertedChangeset', { + async createInvertedChangesetAsync(database: NativeDatabase): Promise { + const result = await invokeWorkerAsync(getWorker(), 'sessionCreateInvertedChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); + return result.buffer as ArrayBuffer; } - createInvertedChangesetSync(database: NativeDatabase): Changeset { - return invokeWorkerSync(getWorker(), 'sessionCreateInvertedChangeset', { + createInvertedChangesetSync(database: NativeDatabase): NativeChangeset { + const result = invokeWorkerSync(getWorker(), 'sessionCreateInvertedChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); + return result.buffer as ArrayBuffer; } - async applyChangesetAsync(database: NativeDatabase, changeset: Changeset): Promise { + async applyChangesetAsync(database: NativeDatabase, changeset: Changeset | NativeChangeset): Promise { await invokeWorkerAsync(getWorker(), 'sessionApplyChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, - changeset, + changeset: convertChangesetInput(changeset), }); } - applyChangesetSync(database: NativeDatabase, changeset: Changeset): void { + applyChangesetSync(database: NativeDatabase, changeset: Changeset | NativeChangeset): void { invokeWorkerSync(getWorker(), 'sessionApplyChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, - changeset, + changeset: convertChangesetInput(changeset), }); } - async invertChangesetAsync(database: NativeDatabase, changeset: Changeset): Promise { - return await invokeWorkerAsync(getWorker(), 'sessionInvertChangeset', { + async invertChangesetAsync(database: NativeDatabase, changeset: Changeset | NativeChangeset): Promise { + const result = await invokeWorkerAsync(getWorker(), 'sessionInvertChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, - changeset, + changeset: convertChangesetInput(changeset), }); + return result.buffer as ArrayBuffer; } - invertChangesetSync(database: NativeDatabase, changeset: Changeset): Changeset { - return invokeWorkerSync(getWorker(), 'sessionInvertChangeset', { + invertChangesetSync(database: NativeDatabase, changeset: Changeset | NativeChangeset): NativeChangeset { + const result = invokeWorkerSync(getWorker(), 'sessionInvertChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, - changeset, + changeset: convertChangesetInput(changeset), }); + return result.buffer as ArrayBuffer; } } From 1f53cebbdddb5086441c16e72f450a2d19a7d38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Klocek?= Date: Wed, 4 Mar 2026 09:27:03 +0100 Subject: [PATCH 3/6] [sqlite] Support ArrayBuffer in statement bind parameters (#42639) # Why Replace copying `Uint8Array` with native`ArrayBuffer`. # How - Replaced native argument types - Used JavaScriptArrayBuffer for sync variant - Used NativeArrayBuffer (copying but thread-safe) for async variant. - Kept user-facing JS types untouched - both `Uint8Array` and `ArrayBuffer` are supported - Updated mocks to translate back to Uint8Array and TS unit tests for the helper func # Test Plan - SQLite test suite - both Android and iOS - Manual testing - TS Unit tests # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/expo-sqlite/CHANGELOG.md | 1 + .../android/src/main/cpp/NativeStatementBinding.cpp | 7 ++++++- .../src/main/java/expo/modules/sqlite/SQLiteModule.kt | 8 ++++---- packages/expo-sqlite/build/NativeStatement.d.ts | 7 ++++--- packages/expo-sqlite/build/NativeStatement.d.ts.map | 2 +- packages/expo-sqlite/build/NativeStatement.js.map | 2 +- packages/expo-sqlite/build/paramUtils.js | 2 +- packages/expo-sqlite/build/paramUtils.js.map | 2 +- packages/expo-sqlite/ios/SQLiteModule.swift | 10 +++++----- packages/expo-sqlite/src/NativeStatement.ts | 10 +++++++--- packages/expo-sqlite/src/__mocks__/ExpoSQLite.ts | 8 ++++++++ .../expo-sqlite/src/__tests__/paramUtils-test.ios.ts | 2 +- packages/expo-sqlite/src/paramUtils.ts | 2 +- 13 files changed, 41 insertions(+), 22 deletions(-) diff --git a/packages/expo-sqlite/CHANGELOG.md b/packages/expo-sqlite/CHANGELOG.md index 7487eafbae7e80..4940220081a294 100644 --- a/packages/expo-sqlite/CHANGELOG.md +++ b/packages/expo-sqlite/CHANGELOG.md @@ -11,6 +11,7 @@ ### 💡 Others - Session changesets now use native `ArrayBuffer`s. ([#42638](https://github.com/expo/expo/pull/42638) by [@barthap](https://github.com/barthap)) +- Statement bind params now use native `ArrayBuffer`s for blob columns. ([#42639](https://github.com/expo/expo/pull/42639) by [@barthap](https://github.com/barthap)) ## 55.0.10 — 2026-02-25 diff --git a/packages/expo-sqlite/android/src/main/cpp/NativeStatementBinding.cpp b/packages/expo-sqlite/android/src/main/cpp/NativeStatementBinding.cpp index 38773325f0a27d..dc1983794f0501 100644 --- a/packages/expo-sqlite/android/src/main/cpp/NativeStatementBinding.cpp +++ b/packages/expo-sqlite/android/src/main/cpp/NativeStatementBinding.cpp @@ -1,5 +1,6 @@ // Copyright 2015-present 650 Industries. All rights reserved. +#include #include "NativeStatementBinding.h" #include @@ -87,7 +88,11 @@ int NativeStatementBinding::bindStatementParam( auto byteArray = jni::static_ref_cast(param); auto data = byteArray->getRegion(0, byteArray->size()); ret = exsqlite3_bind_blob(stmt, index, data.get(), byteArray->size(), - SQLITE_TRANSIENT); + SQLITE_TRANSIENT); + } else if (param->isInstanceOf(jni::JByteBuffer::javaClassStatic())) { + auto byteBuffer = jni::static_ref_cast(param); + ret = exsqlite3_bind_blob(stmt, index, byteBuffer->getDirectAddress(), byteBuffer->getDirectSize(), + SQLITE_TRANSIENT); } else { std::string stringArg; if (param->isInstanceOf(jni::JString::javaClassStatic())) { diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt index 7d4102c6aaafa9..033d0464563850 100644 --- a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt +++ b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt @@ -219,10 +219,10 @@ class SQLiteModule : Module() { return@Constructor NativeStatement() } - AsyncFunction("runAsync") { statement: NativeStatement, database: NativeDatabase, bindParams: Map, bindBlobParams: Map, shouldPassAsArray: Boolean -> + AsyncFunction("runAsync") { statement: NativeStatement, database: NativeDatabase, bindParams: Map, bindBlobParams: Map, shouldPassAsArray: Boolean -> return@AsyncFunction run(statement, database, bindParams, bindBlobParams, shouldPassAsArray) }.runOnQueue(moduleCoroutineScope) - Function("runSync") { statement: NativeStatement, database: NativeDatabase, bindParams: Map, bindBlobParams: Map, shouldPassAsArray: Boolean -> + Function("runSync") { statement: NativeStatement, database: NativeDatabase, bindParams: Map, bindBlobParams: Map, shouldPassAsArray: Boolean -> return@Function run(statement, database, bindParams, bindBlobParams, shouldPassAsArray) } @@ -385,7 +385,7 @@ class SQLiteModule : Module() { } @Throws(AccessClosedResourceException::class, SQLiteErrorException::class) - private fun run(statement: NativeStatement, database: NativeDatabase, bindParams: Map, bindBlobParams: Map, shouldPassAsArray: Boolean): Map { + private fun run(statement: NativeStatement, database: NativeDatabase, bindParams: Map, bindBlobParams: Map, shouldPassAsArray: Boolean): Map { maybeThrowForClosedDatabase(database) maybeThrowForFinalizedStatement(statement) @@ -411,7 +411,7 @@ class SQLiteModule : Module() { for ((key, param) in bindBlobParams) { val index = getBindParamIndex(statement, key, shouldPassAsArray) if (index > 0) { - statement.ref.bindStatementParam(index, param) + statement.ref.bindStatementParam(index, param.toDirectBuffer()) } } diff --git a/packages/expo-sqlite/build/NativeStatement.d.ts b/packages/expo-sqlite/build/NativeStatement.d.ts index 422b229c2558cd..ab6d133d42ca00 100644 --- a/packages/expo-sqlite/build/NativeStatement.d.ts +++ b/packages/expo-sqlite/build/NativeStatement.d.ts @@ -41,11 +41,12 @@ export interface SQLiteRunResult { * const firstRow = await result.getFirstAsync(); * ``` */ -export type SQLiteBindValue = string | number | null | boolean | Uint8Array; +export type SQLiteBindValue = string | number | null | boolean | SQLiteBindBlobValue; export type SQLiteBindParams = Record | SQLiteBindValue[]; export type SQLiteVariadicBindParams = SQLiteBindValue[]; -export type SQLiteBindPrimitiveParams = Record>; -export type SQLiteBindBlobParams = Record; +export type SQLiteBindBlobValue = Uint8Array | ArrayBuffer; +export type SQLiteBindPrimitiveParams = Record>; +export type SQLiteBindBlobParams = Record; export type SQLiteColumnNames = string[]; export type SQLiteColumnValues = any[]; export type SQLiteAnyDatabase = any; diff --git a/packages/expo-sqlite/build/NativeStatement.d.ts.map b/packages/expo-sqlite/build/NativeStatement.d.ts.map index 3332fe680fafbf..d621664c7716d0 100644 --- a/packages/expo-sqlite/build/NativeStatement.d.ts.map +++ b/packages/expo-sqlite/build/NativeStatement.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"NativeStatement.d.ts","sourceRoot":"","sources":["../src/NativeStatement.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,UAAU,CAAC;AAC5E,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,eAAe,EAAE,CAAC;AACnF,MAAM,MAAM,wBAAwB,GAAG,eAAe,EAAE,CAAC;AAEzD,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC,CAAC;AAC7F,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAC9D,MAAM,MAAM,iBAAiB,GAAG,MAAM,EAAE,CAAC;AACzC,MAAM,MAAM,kBAAkB,GAAG,GAAG,EAAE,CAAC;AACvC,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAEpC;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,eAAe;IAG3B,QAAQ,CACb,QAAQ,EAAE,iBAAiB,EAC3B,UAAU,EAAE,yBAAyB,EACrC,cAAc,EAAE,oBAAoB,EACpC,iBAAiB,EAAE,OAAO,GACzB,OAAO,CAAC,eAAe,GAAG;QAAE,cAAc,EAAE,kBAAkB,CAAA;KAAE,CAAC;IAC7D,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,GAAG,SAAS,CAAC;IACtF,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IACvE,UAAU,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IACtD,mBAAmB,IAAI,OAAO,CAAC,iBAAiB,CAAC;IACjD,aAAa,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzD,OAAO,CACZ,QAAQ,EAAE,iBAAiB,EAC3B,UAAU,EAAE,yBAAyB,EACrC,cAAc,EAAE,oBAAoB,EACpC,iBAAiB,EAAE,OAAO,GACzB,eAAe,GAAG;QAAE,cAAc,EAAE,kBAAkB,CAAA;KAAE;IACpD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,GAAG,kBAAkB,GAAG,IAAI,GAAG,SAAS;IAC5E,UAAU,CAAC,QAAQ,EAAE,iBAAiB,GAAG,kBAAkB,EAAE;IAC7D,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAC5C,kBAAkB,IAAI,MAAM,EAAE;IAC9B,YAAY,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;CAGvD"} \ No newline at end of file +{"version":3,"file":"NativeStatement.d.ts","sourceRoot":"","sources":["../src/NativeStatement.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,mBAAmB,CAAC;AACrF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,eAAe,EAAE,CAAC;AACnF,MAAM,MAAM,wBAAwB,GAAG,eAAe,EAAE,CAAC;AACzD,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,WAAW,CAAC;AAE3D,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAC5C,MAAM,EACN,OAAO,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAC9C,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,EAAE,CAAC;AACzC,MAAM,MAAM,kBAAkB,GAAG,GAAG,EAAE,CAAC;AACvC,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAEpC;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,eAAe;IAG3B,QAAQ,CACb,QAAQ,EAAE,iBAAiB,EAC3B,UAAU,EAAE,yBAAyB,EACrC,cAAc,EAAE,oBAAoB,EACpC,iBAAiB,EAAE,OAAO,GACzB,OAAO,CAAC,eAAe,GAAG;QAAE,cAAc,EAAE,kBAAkB,CAAA;KAAE,CAAC;IAC7D,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,GAAG,SAAS,CAAC;IACtF,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IACvE,UAAU,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IACtD,mBAAmB,IAAI,OAAO,CAAC,iBAAiB,CAAC;IACjD,aAAa,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzD,OAAO,CACZ,QAAQ,EAAE,iBAAiB,EAC3B,UAAU,EAAE,yBAAyB,EACrC,cAAc,EAAE,oBAAoB,EACpC,iBAAiB,EAAE,OAAO,GACzB,eAAe,GAAG;QAAE,cAAc,EAAE,kBAAkB,CAAA;KAAE;IACpD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,GAAG,kBAAkB,GAAG,IAAI,GAAG,SAAS;IAC5E,UAAU,CAAC,QAAQ,EAAE,iBAAiB,GAAG,kBAAkB,EAAE;IAC7D,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAC5C,kBAAkB,IAAI,MAAM,EAAE;IAC9B,YAAY,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;CAGvD"} \ No newline at end of file diff --git a/packages/expo-sqlite/build/NativeStatement.js.map b/packages/expo-sqlite/build/NativeStatement.js.map index 29bd11508af91a..4e654be6b40c83 100644 --- a/packages/expo-sqlite/build/NativeStatement.js.map +++ b/packages/expo-sqlite/build/NativeStatement.js.map @@ -1 +1 @@ -{"version":3,"file":"NativeStatement.js","sourceRoot":"","sources":["../src/NativeStatement.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * A result returned by [`SQLiteDatabase.runAsync`](#runasyncsource-params) or [`SQLiteDatabase.runSync`](#runsyncsource-params).\n */\nexport interface SQLiteRunResult {\n /**\n * The last inserted row ID. Returned from the [`sqlite3_last_insert_rowid()`](https://www.sqlite.org/c3ref/last_insert_rowid.html) function.\n */\n lastInsertRowId: number;\n\n /**\n * The number of rows affected. Returned from the [`sqlite3_changes()`](https://www.sqlite.org/c3ref/changes.html) function.\n */\n changes: number;\n}\n\n/**\n * Bind parameters to the prepared statement.\n * You can either pass the parameters in the following forms:\n *\n * @example\n * A single array for unnamed parameters.\n * ```ts\n * const statement = await db.prepareAsync('SELECT * FROM test WHERE value = ? AND intValue = ?');\n * const result = await statement.executeAsync(['test1', 789]);\n * const firstRow = await result.getFirstAsync();\n * ```\n *\n * @example\n * Variadic arguments for unnamed parameters.\n * ```ts\n * const statement = await db.prepareAsync('SELECT * FROM test WHERE value = ? AND intValue = ?');\n * const result = await statement.executeAsync('test1', 789);\n * const firstRow = await result.getFirstAsync();\n * ```\n *\n * @example\n * A single object for [named parameters](https://www.sqlite.org/lang_expr.html)\n *\n * We support multiple named parameter forms such as `:VVV`, `@VVV`, and `$VVV`. We recommend using `$VVV` because JavaScript allows using `$` in identifiers without escaping.\n * ```ts\n * const statement = await db.prepareAsync('SELECT * FROM test WHERE value = $value AND intValue = $intValue');\n * const result = await statement.executeAsync({ $value: 'test1', $intValue: 789 });\n * const firstRow = await result.getFirstAsync();\n * ```\n */\nexport type SQLiteBindValue = string | number | null | boolean | Uint8Array;\nexport type SQLiteBindParams = Record | SQLiteBindValue[];\nexport type SQLiteVariadicBindParams = SQLiteBindValue[];\n\nexport type SQLiteBindPrimitiveParams = Record>;\nexport type SQLiteBindBlobParams = Record;\nexport type SQLiteColumnNames = string[];\nexport type SQLiteColumnValues = any[];\nexport type SQLiteAnyDatabase = any;\n\n/**\n * A class that represents an instance of the SQLite statement.\n */\nexport declare class NativeStatement {\n //#region Asynchronous API\n\n public runAsync(\n database: SQLiteAnyDatabase,\n bindParams: SQLiteBindPrimitiveParams,\n bindBlobParams: SQLiteBindBlobParams,\n shouldPassAsArray: boolean\n ): Promise;\n public stepAsync(database: SQLiteAnyDatabase): Promise;\n public getAllAsync(database: SQLiteAnyDatabase): Promise;\n public resetAsync(database: SQLiteAnyDatabase): Promise;\n public getColumnNamesAsync(): Promise;\n public finalizeAsync(database: SQLiteAnyDatabase): Promise;\n\n //#endregion\n\n //#region Synchronous API\n\n public runSync(\n database: SQLiteAnyDatabase,\n bindParams: SQLiteBindPrimitiveParams,\n bindBlobParams: SQLiteBindBlobParams,\n shouldPassAsArray: boolean\n ): SQLiteRunResult & { firstRowValues: SQLiteColumnValues };\n public stepSync(database: SQLiteAnyDatabase): SQLiteColumnValues | null | undefined;\n public getAllSync(database: SQLiteAnyDatabase): SQLiteColumnValues[];\n public resetSync(database: SQLiteAnyDatabase): void;\n public getColumnNamesSync(): string[];\n public finalizeSync(database: SQLiteAnyDatabase): void;\n\n //#endregion\n}\n"]} \ No newline at end of file +{"version":3,"file":"NativeStatement.js","sourceRoot":"","sources":["../src/NativeStatement.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * A result returned by [`SQLiteDatabase.runAsync`](#runasyncsource-params) or [`SQLiteDatabase.runSync`](#runsyncsource-params).\n */\nexport interface SQLiteRunResult {\n /**\n * The last inserted row ID. Returned from the [`sqlite3_last_insert_rowid()`](https://www.sqlite.org/c3ref/last_insert_rowid.html) function.\n */\n lastInsertRowId: number;\n\n /**\n * The number of rows affected. Returned from the [`sqlite3_changes()`](https://www.sqlite.org/c3ref/changes.html) function.\n */\n changes: number;\n}\n\n/**\n * Bind parameters to the prepared statement.\n * You can either pass the parameters in the following forms:\n *\n * @example\n * A single array for unnamed parameters.\n * ```ts\n * const statement = await db.prepareAsync('SELECT * FROM test WHERE value = ? AND intValue = ?');\n * const result = await statement.executeAsync(['test1', 789]);\n * const firstRow = await result.getFirstAsync();\n * ```\n *\n * @example\n * Variadic arguments for unnamed parameters.\n * ```ts\n * const statement = await db.prepareAsync('SELECT * FROM test WHERE value = ? AND intValue = ?');\n * const result = await statement.executeAsync('test1', 789);\n * const firstRow = await result.getFirstAsync();\n * ```\n *\n * @example\n * A single object for [named parameters](https://www.sqlite.org/lang_expr.html)\n *\n * We support multiple named parameter forms such as `:VVV`, `@VVV`, and `$VVV`. We recommend using `$VVV` because JavaScript allows using `$` in identifiers without escaping.\n * ```ts\n * const statement = await db.prepareAsync('SELECT * FROM test WHERE value = $value AND intValue = $intValue');\n * const result = await statement.executeAsync({ $value: 'test1', $intValue: 789 });\n * const firstRow = await result.getFirstAsync();\n * ```\n */\nexport type SQLiteBindValue = string | number | null | boolean | SQLiteBindBlobValue;\nexport type SQLiteBindParams = Record | SQLiteBindValue[];\nexport type SQLiteVariadicBindParams = SQLiteBindValue[];\nexport type SQLiteBindBlobValue = Uint8Array | ArrayBuffer;\n\nexport type SQLiteBindPrimitiveParams = Record<\n string,\n Exclude\n>;\nexport type SQLiteBindBlobParams = Record;\nexport type SQLiteColumnNames = string[];\nexport type SQLiteColumnValues = any[];\nexport type SQLiteAnyDatabase = any;\n\n/**\n * A class that represents an instance of the SQLite statement.\n */\nexport declare class NativeStatement {\n //#region Asynchronous API\n\n public runAsync(\n database: SQLiteAnyDatabase,\n bindParams: SQLiteBindPrimitiveParams,\n bindBlobParams: SQLiteBindBlobParams,\n shouldPassAsArray: boolean\n ): Promise;\n public stepAsync(database: SQLiteAnyDatabase): Promise;\n public getAllAsync(database: SQLiteAnyDatabase): Promise;\n public resetAsync(database: SQLiteAnyDatabase): Promise;\n public getColumnNamesAsync(): Promise;\n public finalizeAsync(database: SQLiteAnyDatabase): Promise;\n\n //#endregion\n\n //#region Synchronous API\n\n public runSync(\n database: SQLiteAnyDatabase,\n bindParams: SQLiteBindPrimitiveParams,\n bindBlobParams: SQLiteBindBlobParams,\n shouldPassAsArray: boolean\n ): SQLiteRunResult & { firstRowValues: SQLiteColumnValues };\n public stepSync(database: SQLiteAnyDatabase): SQLiteColumnValues | null | undefined;\n public getAllSync(database: SQLiteAnyDatabase): SQLiteColumnValues[];\n public resetSync(database: SQLiteAnyDatabase): void;\n public getColumnNamesSync(): string[];\n public finalizeSync(database: SQLiteAnyDatabase): void;\n\n //#endregion\n}\n"]} \ No newline at end of file diff --git a/packages/expo-sqlite/build/paramUtils.js b/packages/expo-sqlite/build/paramUtils.js index c251be7622676e..8610594e10d902 100644 --- a/packages/expo-sqlite/build/paramUtils.js +++ b/packages/expo-sqlite/build/paramUtils.js @@ -24,7 +24,7 @@ export function normalizeParams(...params) { const blobParams = {}; for (const key in bindParams) { const value = bindParams[key]; - if (value instanceof Uint8Array) { + if (value instanceof Uint8Array || value instanceof ArrayBuffer) { blobParams[key] = value; } else if (typeof value === 'boolean') { diff --git a/packages/expo-sqlite/build/paramUtils.js.map b/packages/expo-sqlite/build/paramUtils.js.map index d6bd9d524ec1cf..9d1a231ac31bed 100644 --- a/packages/expo-sqlite/build/paramUtils.js.map +++ b/packages/expo-sqlite/build/paramUtils.js.map @@ -1 +1 @@ -{"version":3,"file":"paramUtils.js","sourceRoot":"","sources":["../src/paramUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n SQLiteBindBlobParams,\n SQLiteBindParams,\n SQLiteBindPrimitiveParams,\n SQLiteBindValue,\n type SQLiteColumnNames,\n type SQLiteColumnValues,\n} from './NativeStatement';\n\n/**\n * Normalize the bind params to data structure that can be passed to native module.\n * The data structure is a tuple of [primitiveParams, blobParams, shouldPassAsArray].\n * @hidden\n */\nexport function normalizeParams(\n ...params: any[]\n): [SQLiteBindPrimitiveParams, SQLiteBindBlobParams, boolean] {\n let bindParams = params.length > 1 ? params : (params[0] as SQLiteBindParams);\n if (bindParams == null) {\n bindParams = [];\n }\n if (\n typeof bindParams !== 'object' ||\n bindParams instanceof ArrayBuffer ||\n ArrayBuffer.isView(bindParams)\n ) {\n bindParams = [bindParams];\n }\n const shouldPassAsArray = Array.isArray(bindParams);\n if (Array.isArray(bindParams)) {\n bindParams = bindParams.reduce>((acc, value, index) => {\n acc[index] = value;\n return acc;\n }, {});\n }\n\n const primitiveParams: SQLiteBindPrimitiveParams = {};\n const blobParams: SQLiteBindBlobParams = {};\n for (const key in bindParams) {\n const value = bindParams[key];\n if (value instanceof Uint8Array) {\n blobParams[key] = value;\n } else if (typeof value === 'boolean') {\n primitiveParams[key] = value ? 1 : 0;\n } else {\n primitiveParams[key] = value;\n }\n }\n\n return [primitiveParams, blobParams, shouldPassAsArray];\n}\n\n/**\n * Compose `columnNames` and `columnValues` to an row object.\n * @hidden\n */\nexport function composeRow(columnNames: SQLiteColumnNames, columnValues: SQLiteColumnValues): T {\n // TODO(cedric): make these types more generic and tighten the returned object type based on provided column names/values\n const row: { [key in SQLiteColumnNames[number]]: SQLiteColumnValues[number] } = {};\n if (columnNames.length !== columnValues.length) {\n throw new Error(\n `Column names and values count mismatch. Names: ${columnNames.length}, Values: ${columnValues.length}`\n );\n }\n for (let i = 0; i < columnNames.length; i++) {\n row[columnNames[i]] = columnValues[i];\n }\n return row as T;\n}\n\n/**\n * Compose `columnNames` and `columnValuesList` to an array of row objects.\n * @hidden\n */\nexport function composeRows(\n columnNames: SQLiteColumnNames,\n columnValuesList: SQLiteColumnValues[]\n): T[] {\n if (columnValuesList.length === 0) {\n return [];\n }\n if (columnNames.length !== columnValuesList[0].length) {\n // We only check the first row because SQLite returns the same column count for all rows.\n throw new Error(\n `Column names and values count mismatch. Names: ${columnNames.length}, Values: ${columnValuesList[0].length}`\n );\n }\n const results: T[] = [];\n for (const columnValues of columnValuesList) {\n // TODO(cedric): make these types more generic and tighten the returned object type based on provided column names/values\n const row: { [key in SQLiteColumnNames[number]]: SQLiteColumnValues[number] } = {};\n for (let i = 0; i < columnNames.length; i++) {\n row[columnNames[i]] = columnValues[i];\n }\n results.push(row as T);\n }\n return results;\n}\n\n/**\n * Normalize the index for the Storage.getKeyByIndexSync and Storage.getKeyByIndexAsync methods.\n * @returns The normalized index or `null` if the index is out of bounds.\n * @hidden\n */\nexport function normalizeStorageIndex(index: any): number | null {\n const value = Math.floor(Number(index));\n\n // Boundary checks\n if (Object.is(value, -0)) {\n return 0;\n }\n if (!Number.isSafeInteger(value)) {\n // Chromium uses zero index when the index is out of bounds\n return 0;\n }\n if (value < 0) {\n return null;\n }\n return value;\n}\n"]} \ No newline at end of file +{"version":3,"file":"paramUtils.js","sourceRoot":"","sources":["../src/paramUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n SQLiteBindBlobParams,\n SQLiteBindParams,\n SQLiteBindPrimitiveParams,\n SQLiteBindValue,\n type SQLiteColumnNames,\n type SQLiteColumnValues,\n} from './NativeStatement';\n\n/**\n * Normalize the bind params to data structure that can be passed to native module.\n * The data structure is a tuple of [primitiveParams, blobParams, shouldPassAsArray].\n * @hidden\n */\nexport function normalizeParams(\n ...params: any[]\n): [SQLiteBindPrimitiveParams, SQLiteBindBlobParams, boolean] {\n let bindParams = params.length > 1 ? params : (params[0] as SQLiteBindParams);\n if (bindParams == null) {\n bindParams = [];\n }\n if (\n typeof bindParams !== 'object' ||\n bindParams instanceof ArrayBuffer ||\n ArrayBuffer.isView(bindParams)\n ) {\n bindParams = [bindParams];\n }\n const shouldPassAsArray = Array.isArray(bindParams);\n if (Array.isArray(bindParams)) {\n bindParams = bindParams.reduce>((acc, value, index) => {\n acc[index] = value;\n return acc;\n }, {});\n }\n\n const primitiveParams: SQLiteBindPrimitiveParams = {};\n const blobParams: SQLiteBindBlobParams = {};\n for (const key in bindParams) {\n const value = bindParams[key];\n if (value instanceof Uint8Array || value instanceof ArrayBuffer) {\n blobParams[key] = value;\n } else if (typeof value === 'boolean') {\n primitiveParams[key] = value ? 1 : 0;\n } else {\n primitiveParams[key] = value;\n }\n }\n\n return [primitiveParams, blobParams, shouldPassAsArray];\n}\n\n/**\n * Compose `columnNames` and `columnValues` to an row object.\n * @hidden\n */\nexport function composeRow(columnNames: SQLiteColumnNames, columnValues: SQLiteColumnValues): T {\n // TODO(cedric): make these types more generic and tighten the returned object type based on provided column names/values\n const row: { [key in SQLiteColumnNames[number]]: SQLiteColumnValues[number] } = {};\n if (columnNames.length !== columnValues.length) {\n throw new Error(\n `Column names and values count mismatch. Names: ${columnNames.length}, Values: ${columnValues.length}`\n );\n }\n for (let i = 0; i < columnNames.length; i++) {\n row[columnNames[i]] = columnValues[i];\n }\n return row as T;\n}\n\n/**\n * Compose `columnNames` and `columnValuesList` to an array of row objects.\n * @hidden\n */\nexport function composeRows(\n columnNames: SQLiteColumnNames,\n columnValuesList: SQLiteColumnValues[]\n): T[] {\n if (columnValuesList.length === 0) {\n return [];\n }\n if (columnNames.length !== columnValuesList[0].length) {\n // We only check the first row because SQLite returns the same column count for all rows.\n throw new Error(\n `Column names and values count mismatch. Names: ${columnNames.length}, Values: ${columnValuesList[0].length}`\n );\n }\n const results: T[] = [];\n for (const columnValues of columnValuesList) {\n // TODO(cedric): make these types more generic and tighten the returned object type based on provided column names/values\n const row: { [key in SQLiteColumnNames[number]]: SQLiteColumnValues[number] } = {};\n for (let i = 0; i < columnNames.length; i++) {\n row[columnNames[i]] = columnValues[i];\n }\n results.push(row as T);\n }\n return results;\n}\n\n/**\n * Normalize the index for the Storage.getKeyByIndexSync and Storage.getKeyByIndexAsync methods.\n * @returns The normalized index or `null` if the index is out of bounds.\n * @hidden\n */\nexport function normalizeStorageIndex(index: any): number | null {\n const value = Math.floor(Number(index));\n\n // Boundary checks\n if (Object.is(value, -0)) {\n return 0;\n }\n if (!Number.isSafeInteger(value)) {\n // Chromium uses zero index when the index is out of bounds\n return 0;\n }\n if (value < 0) {\n return null;\n }\n return value;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-sqlite/ios/SQLiteModule.swift b/packages/expo-sqlite/ios/SQLiteModule.swift index f1c32d9602a1e7..029d5b626df496 100644 --- a/packages/expo-sqlite/ios/SQLiteModule.swift +++ b/packages/expo-sqlite/ios/SQLiteModule.swift @@ -196,10 +196,10 @@ public final class SQLiteModule: Module { // swiftlint:disable line_length - AsyncFunction("runAsync") { (statement: NativeStatement, database: NativeDatabase, bindParams: [String: Any], bindBlobParams: [String: Data], shouldPassAsArray: Bool) -> [String: Any] in + AsyncFunction("runAsync") { (statement: NativeStatement, database: NativeDatabase, bindParams: [String: Any], bindBlobParams: [String: NativeArrayBuffer], shouldPassAsArray: Bool) -> [String: Any] in return try run(statement: statement, database: database, bindParams: bindParams, bindBlobParams: bindBlobParams, shouldPassAsArray: shouldPassAsArray) }.runOnQueue(moduleQueue) - Function("runSync") { (statement: NativeStatement, database: NativeDatabase, bindParams: [String: Any], bindBlobParams: [String: Data], shouldPassAsArray: Bool) -> [String: Any] in + Function("runSync") { (statement: NativeStatement, database: NativeDatabase, bindParams: [String: Any], bindBlobParams: [String: JavaScriptArrayBuffer], shouldPassAsArray: Bool) -> [String: Any] in return try run(statement: statement, database: database, bindParams: bindParams, bindBlobParams: bindBlobParams, shouldPassAsArray: shouldPassAsArray) } @@ -385,7 +385,7 @@ public final class SQLiteModule: Module { // swiftlint:disable line_length - private func run(statement: NativeStatement, database: NativeDatabase, bindParams: [String: Any], bindBlobParams: [String: Data], shouldPassAsArray: Bool) throws -> [String: Any] { + private func run(statement: NativeStatement, database: NativeDatabase, bindParams: [String: Any], bindBlobParams: [String: ArrayBuffer], shouldPassAsArray: Bool) throws -> [String: Any] { try maybeThrowForClosedDatabase(database) try maybeThrowForFinalizedStatement(statement) @@ -639,9 +639,9 @@ public final class SQLiteModule: Module { exsqlite3_bind_double(instance, index, param) case let param as String: exsqlite3_bind_text(instance, index, param, -1, SQLITE_TRANSIENT) - case let param as Data: + case let param as ArrayBuffer: _ = param.withUnsafeBytes { - exsqlite3_bind_blob(instance, index, $0.baseAddress, Int32(param.count), SQLITE_TRANSIENT) + exsqlite3_bind_blob(instance, index, $0.baseAddress, Int32(param.byteLength), SQLITE_TRANSIENT) } case let param as Bool: exsqlite3_bind_int(instance, index, param ? 1 : 0) diff --git a/packages/expo-sqlite/src/NativeStatement.ts b/packages/expo-sqlite/src/NativeStatement.ts index d23b0c9831ddbd..f348856c0b5cb3 100644 --- a/packages/expo-sqlite/src/NativeStatement.ts +++ b/packages/expo-sqlite/src/NativeStatement.ts @@ -43,12 +43,16 @@ export interface SQLiteRunResult { * const firstRow = await result.getFirstAsync(); * ``` */ -export type SQLiteBindValue = string | number | null | boolean | Uint8Array; +export type SQLiteBindValue = string | number | null | boolean | SQLiteBindBlobValue; export type SQLiteBindParams = Record | SQLiteBindValue[]; export type SQLiteVariadicBindParams = SQLiteBindValue[]; +export type SQLiteBindBlobValue = Uint8Array | ArrayBuffer; -export type SQLiteBindPrimitiveParams = Record>; -export type SQLiteBindBlobParams = Record; +export type SQLiteBindPrimitiveParams = Record< + string, + Exclude +>; +export type SQLiteBindBlobParams = Record; export type SQLiteColumnNames = string[]; export type SQLiteColumnValues = any[]; export type SQLiteAnyDatabase = any; diff --git a/packages/expo-sqlite/src/__mocks__/ExpoSQLite.ts b/packages/expo-sqlite/src/__mocks__/ExpoSQLite.ts index e902ff3bafeac1..3bc57811016915 100644 --- a/packages/expo-sqlite/src/__mocks__/ExpoSQLite.ts +++ b/packages/expo-sqlite/src/__mocks__/ExpoSQLite.ts @@ -230,6 +230,10 @@ function normalizeSQLite3Args( result[Number(key)] = value; } for (const [key, value] of Object.entries(bindBlobParams)) { + if (value instanceof ArrayBuffer) { + result[Number(key)] = new Uint8Array(value) as any; + continue; + } result[Number(key)] = value; } return result; @@ -243,6 +247,10 @@ function normalizeSQLite3Args( } for (const [key, value] of Object.entries(bindBlobParams)) { const normalizedKey = key.replace(replaceRegexp, ''); + if (value instanceof ArrayBuffer) { + result[normalizedKey] = new Uint8Array(value) as any; + continue; + } result[normalizedKey] = value; } return result; diff --git a/packages/expo-sqlite/src/__tests__/paramUtils-test.ios.ts b/packages/expo-sqlite/src/__tests__/paramUtils-test.ios.ts index 2c89c58529be77..cd6fb091bcde6d 100644 --- a/packages/expo-sqlite/src/__tests__/paramUtils-test.ios.ts +++ b/packages/expo-sqlite/src/__tests__/paramUtils-test.ios.ts @@ -47,7 +47,7 @@ describe(normalizeParams, () => { it('should support blob params', () => { const blob = new Uint8Array([0x00]); - const blob2 = new Uint8Array([0x01]); + const blob2 = new Uint8Array([0x01]).buffer; expect(normalizeParams(blob)).toStrictEqual([{}, { 0: blob }, true]); expect(normalizeParams('hello', blob)).toStrictEqual([{ 0: 'hello' }, { 1: blob }, true]); expect(normalizeParams(['hello', blob, 'world', blob2])).toStrictEqual([ diff --git a/packages/expo-sqlite/src/paramUtils.ts b/packages/expo-sqlite/src/paramUtils.ts index 464eba9ee9c657..7576564f642987 100644 --- a/packages/expo-sqlite/src/paramUtils.ts +++ b/packages/expo-sqlite/src/paramUtils.ts @@ -38,7 +38,7 @@ export function normalizeParams( const blobParams: SQLiteBindBlobParams = {}; for (const key in bindParams) { const value = bindParams[key]; - if (value instanceof Uint8Array) { + if (value instanceof Uint8Array || value instanceof ArrayBuffer) { blobParams[key] = value; } else if (typeof value === 'boolean') { primitiveParams[key] = value ? 1 : 0; From 507a80679377084ac507d49941bc243a9766b521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Klocek?= Date: Wed, 4 Mar 2026 09:29:38 +0100 Subject: [PATCH 4/6] [sqlite][Android] Use ArrayBuffers for returning blob columns (#42640) # Why Replace copying `Uint8Array` with `NativeArrayBuffer` on Android. Only one copy is done instead of two: 1. sqlite3 -> ArrayBuffer; 2. returning ArrayBuffer is now zero-copy iOS is handled later # How - Updated native code to use `ByteBuffer` / `ArrayBuffer` - Updated JS to convert back `ArrayBuffer` columns to `Uint8Array` (backwards compat) # Test Plan - Android test suite - iOS test suite - to make sure it's not broken and backward compatible # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/expo-sqlite/CHANGELOG.md | 1 + .../src/main/cpp/NativeStatementBinding.cpp | 12 ++++--- .../expo/modules/sqlite/NativeStatement.kt | 12 +++++++ .../java/expo/modules/sqlite/SQLiteModule.kt | 6 ++-- packages/expo-sqlite/build/SQLiteStatement.js | 24 +++++++++----- .../expo-sqlite/build/SQLiteStatement.js.map | 2 +- packages/expo-sqlite/src/SQLiteStatement.ts | 33 ++++++++++++++----- 7 files changed, 65 insertions(+), 25 deletions(-) diff --git a/packages/expo-sqlite/CHANGELOG.md b/packages/expo-sqlite/CHANGELOG.md index 4940220081a294..8a0c3700854210 100644 --- a/packages/expo-sqlite/CHANGELOG.md +++ b/packages/expo-sqlite/CHANGELOG.md @@ -12,6 +12,7 @@ - Session changesets now use native `ArrayBuffer`s. ([#42638](https://github.com/expo/expo/pull/42638) by [@barthap](https://github.com/barthap)) - Statement bind params now use native `ArrayBuffer`s for blob columns. ([#42639](https://github.com/expo/expo/pull/42639) by [@barthap](https://github.com/barthap)) +- [Android] Returned blob columns now use native `ArrayBuffer`s. ([#42640](https://github.com/expo/expo/pull/42640) by [@barthap](https://github.com/barthap)) ## 55.0.10 — 2026-02-25 diff --git a/packages/expo-sqlite/android/src/main/cpp/NativeStatementBinding.cpp b/packages/expo-sqlite/android/src/main/cpp/NativeStatementBinding.cpp index dc1983794f0501..6c3995f3896490 100644 --- a/packages/expo-sqlite/android/src/main/cpp/NativeStatementBinding.cpp +++ b/packages/expo-sqlite/android/src/main/cpp/NativeStatementBinding.cpp @@ -149,11 +149,13 @@ jni::local_ref NativeStatementBinding::getColumnValue(int index) { } case SQLITE_BLOB: { size_t length = static_cast(exsqlite3_column_bytes(stmt, index)); - auto byteArray = jni::JArrayByte::newArray(length); - byteArray->setRegion( - 0, length, - static_cast(exsqlite3_column_blob(stmt, index))); - return byteArray; + auto byteBuffer = jni::JByteBuffer::allocateDirect(length); + memcpy( + byteBuffer->getDirectAddress(), + exsqlite3_column_blob(stmt, index), + length + ); + return byteBuffer; } case SQLITE_NULL: { return nullptr; diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/NativeStatement.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/NativeStatement.kt index 7fc2a9372f79d8..4764c18632840d 100644 --- a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/NativeStatement.kt +++ b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/NativeStatement.kt @@ -2,7 +2,9 @@ package expo.modules.sqlite +import expo.modules.kotlin.jni.NativeArrayBuffer import expo.modules.kotlin.sharedobjects.SharedRef +import java.nio.ByteBuffer internal class NativeStatement : SharedRef(NativeStatementBinding()) { var isFinalized = false @@ -19,4 +21,14 @@ internal class NativeStatement : SharedRef(NativeStateme override fun hashCode(): Int { return ref.hashCode() } + + fun getTransformedColumnValues(): SQLiteColumnValues { + val columnValueList = ref.getColumnValues().map { + when (it) { + is ByteBuffer -> NativeArrayBuffer(it) + else -> it + } + } + return ArrayList(columnValueList) + } } diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt index 033d0464563850..3e668be157f276 100644 --- a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt +++ b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt @@ -421,7 +421,7 @@ class SQLiteModule : Module() { } val firstRowValues: SQLiteColumnValues = if (ret == NativeDatabaseBinding.SQLITE_ROW) { - statement.ref.getColumnValues() + statement.getTransformedColumnValues() } else { arrayListOf() } @@ -439,7 +439,7 @@ class SQLiteModule : Module() { maybeThrowForFinalizedStatement(statement) val ret = statement.ref.sqlite3_step() if (ret == NativeDatabaseBinding.SQLITE_ROW) { - return statement.ref.getColumnValues() + return statement.getTransformedColumnValues() } if (ret != NativeDatabaseBinding.SQLITE_DONE) { throw SQLiteErrorException(database.ref.convertSqlLiteErrorToString()) @@ -455,7 +455,7 @@ class SQLiteModule : Module() { while (true) { val ret = statement.ref.sqlite3_step() if (ret == NativeDatabaseBinding.SQLITE_ROW) { - columnValuesList.add(statement.ref.getColumnValues()) + columnValuesList.add(statement.getTransformedColumnValues()) continue } else if (ret == NativeDatabaseBinding.SQLITE_DONE) { break diff --git a/packages/expo-sqlite/build/SQLiteStatement.js b/packages/expo-sqlite/build/SQLiteStatement.js index ab51bea231a1fb..dfa240a9658860 100644 --- a/packages/expo-sqlite/build/SQLiteStatement.js +++ b/packages/expo-sqlite/build/SQLiteStatement.js @@ -84,7 +84,7 @@ export class SQLiteStatement { * This is done by `Object.defineProperties` to add the properties to the AsyncGenerator. */ async function createSQLiteExecuteAsyncResult(database, statement, firstRowValues, options) { - const instance = new SQLiteExecuteAsyncResultImpl(database, statement, firstRowValues, options); + const instance = new SQLiteExecuteAsyncResultImpl(database, statement, firstRowValues ? processNativeRow(firstRowValues) : null, options); const generator = instance.generatorAsync(); Object.defineProperties(generator, { lastInsertRowId: { @@ -119,7 +119,7 @@ async function createSQLiteExecuteAsyncResult(database, statement, firstRowValue * Create the `SQLiteExecuteSyncResult` instance. */ function createSQLiteExecuteSyncResult(database, statement, firstRowValues, options) { - const instance = new SQLiteExecuteSyncResultImpl(database, statement, firstRowValues, options); + const instance = new SQLiteExecuteSyncResultImpl(database, statement, firstRowValues ? processNativeRow(firstRowValues) : firstRowValues, options); const generator = instance.generatorSync(); Object.defineProperties(generator, { lastInsertRowId: { @@ -175,7 +175,7 @@ class SQLiteExecuteAsyncResultImpl { } const firstRow = await this.statement.stepAsync(this.database); return firstRow != null - ? composeRowIfNeeded(this.options.rawResult, columnNames, firstRow) + ? composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(firstRow)) : null; } async getAllAsync() { @@ -189,7 +189,8 @@ class SQLiteExecuteAsyncResultImpl { return []; } const columnNames = await this.getColumnNamesAsync(); - const allRows = await this.statement.getAllAsync(this.database); + const nativeRows = await this.statement.getAllAsync(this.database); + const allRows = processNativeRows(nativeRows); if (firstRowValues != null && firstRowValues.length > 0) { return composeRowsIfNeeded(this.options.rawResult, columnNames, [ firstRowValues, @@ -209,7 +210,7 @@ class SQLiteExecuteAsyncResultImpl { do { result = await this.statement.stepAsync(this.database); if (result != null) { - yield composeRowIfNeeded(this.options.rawResult, columnNames, result); + yield composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(result)); } } while (result != null); } @@ -257,7 +258,7 @@ class SQLiteExecuteSyncResultImpl { } const firstRow = this.statement.stepSync(this.database); return firstRow != null - ? composeRowIfNeeded(this.options.rawResult, columnNames, firstRow) + ? composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(firstRow)) : null; } getAllSync() { @@ -270,7 +271,8 @@ class SQLiteExecuteSyncResultImpl { return []; } const columnNames = this.getColumnNamesSync(); - const allRows = this.statement.getAllSync(this.database); + const nativeRows = this.statement.getAllSync(this.database); + const allRows = processNativeRows(nativeRows); if (firstRowValues != null && firstRowValues.length > 0) { return composeRowsIfNeeded(this.options.rawResult, columnNames, [ firstRowValues, @@ -289,7 +291,7 @@ class SQLiteExecuteSyncResultImpl { do { result = this.statement.stepSync(this.database); if (result != null) { - yield composeRowIfNeeded(this.options.rawResult, columnNames, result); + yield composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(result)); } } while (result != null); } @@ -323,5 +325,11 @@ function composeRowsIfNeeded(rawResult, columnNames, columnValuesList) { ? columnValuesList // T[] would be a ValuesOf<>[] from caller : composeRows(columnNames, columnValuesList); } +function processNativeRow(nativeRow) { + return nativeRow?.map((column) => column instanceof ArrayBuffer ? new Uint8Array(column) : column); +} +function processNativeRows(nativeRows) { + return nativeRows.map(processNativeRow); +} //#endregion //# sourceMappingURL=SQLiteStatement.js.map \ No newline at end of file diff --git a/packages/expo-sqlite/build/SQLiteStatement.js.map b/packages/expo-sqlite/build/SQLiteStatement.js.map index 5143392888051e..71a0cd0d27383b 100644 --- a/packages/expo-sqlite/build/SQLiteStatement.js.map +++ b/packages/expo-sqlite/build/SQLiteStatement.js.map @@ -1 +1 @@ -{"version":3,"file":"SQLiteStatement.js","sourceRoot":"","sources":["../src/SQLiteStatement.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeDatabase } from './NativeDatabase';\nimport {\n SQLiteBindParams,\n SQLiteBindValue,\n NativeStatement,\n SQLiteVariadicBindParams,\n type SQLiteAnyDatabase,\n type SQLiteColumnNames,\n type SQLiteColumnValues,\n type SQLiteRunResult,\n} from './NativeStatement';\nimport { composeRow, composeRows, normalizeParams } from './paramUtils';\n\nexport { SQLiteBindParams, SQLiteBindValue, SQLiteRunResult, SQLiteVariadicBindParams };\n\ntype ValuesOf = T[keyof T][];\n\n/**\n * A prepared statement returned by [`SQLiteDatabase.prepareAsync()`](#prepareasyncsource) or [`SQLiteDatabase.prepareSync()`](#preparesyncsource) that can be binded with parameters and executed.\n */\nexport class SQLiteStatement {\n constructor(\n private readonly nativeDatabase: NativeDatabase,\n private readonly nativeStatement: NativeStatement\n ) {}\n\n //#region Asynchronous API\n\n /**\n * Run the prepared statement and return the [`SQLiteExecuteAsyncResult`](#sqliteexecuteasyncresult) instance.\n * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`SQLiteBindValue`](#sqlitebindvalue) for more information about binding values.\n */\n public executeAsync(params: SQLiteBindParams): Promise>;\n /**\n * @hidden\n */\n public executeAsync(...params: SQLiteVariadicBindParams): Promise>;\n public async executeAsync(...params: unknown[]): Promise> {\n const { lastInsertRowId, changes, firstRowValues } = await this.nativeStatement.runAsync(\n this.nativeDatabase,\n ...normalizeParams(...params)\n );\n return createSQLiteExecuteAsyncResult(\n this.nativeDatabase,\n this.nativeStatement,\n firstRowValues,\n {\n rawResult: false,\n lastInsertRowId,\n changes,\n }\n );\n }\n\n /**\n * Similar to [`executeAsync()`](#executeasyncparams) but returns the raw value array result instead of the row objects.\n * @hidden Advanced use only.\n */\n public executeForRawResultAsync(\n params: SQLiteBindParams\n ): Promise>>;\n /**\n * @hidden\n */\n public executeForRawResultAsync(\n ...params: SQLiteVariadicBindParams\n ): Promise>>;\n public async executeForRawResultAsync(\n ...params: unknown[]\n ): Promise>> {\n const { lastInsertRowId, changes, firstRowValues } = await this.nativeStatement.runAsync(\n this.nativeDatabase,\n ...normalizeParams(...params)\n );\n return createSQLiteExecuteAsyncResult>(\n this.nativeDatabase,\n this.nativeStatement,\n firstRowValues,\n {\n rawResult: true,\n lastInsertRowId,\n changes,\n }\n );\n }\n\n /**\n * Get the column names of the prepared statement.\n */\n public getColumnNamesAsync(): Promise {\n return this.nativeStatement.getColumnNamesAsync();\n }\n\n /**\n * Finalize the prepared statement. This will call the [`sqlite3_finalize()`](https://www.sqlite.org/c3ref/finalize.html) C function under the hood.\n *\n * Attempting to access a finalized statement will result in an error.\n * > **Note:** While `expo-sqlite` will automatically finalize any orphaned prepared statements upon closing the database, it is considered best practice\n * > to manually finalize prepared statements as soon as they are no longer needed. This helps to prevent resource leaks.\n * > You can use the `try...finally` statement to ensure that prepared statements are finalized even if an error occurs.\n */\n public async finalizeAsync(): Promise {\n await this.nativeStatement.finalizeAsync(this.nativeDatabase);\n }\n\n //#endregion\n\n //#region Synchronous API\n\n /**\n * Run the prepared statement and return the [`SQLiteExecuteSyncResult`](#sqliteexecutesyncresult) instance.\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`SQLiteBindValue`](#sqlitebindvalue) for more information about binding values.\n */\n public executeSync(params: SQLiteBindParams): SQLiteExecuteSyncResult;\n /**\n * @hidden\n */\n public executeSync(...params: SQLiteVariadicBindParams): SQLiteExecuteSyncResult;\n public executeSync(...params: unknown[]): SQLiteExecuteSyncResult {\n const { lastInsertRowId, changes, firstRowValues } = this.nativeStatement.runSync(\n this.nativeDatabase,\n ...normalizeParams(...params)\n );\n return createSQLiteExecuteSyncResult(\n this.nativeDatabase,\n this.nativeStatement,\n firstRowValues,\n {\n rawResult: false,\n lastInsertRowId,\n changes,\n }\n );\n }\n\n /**\n * Similar to [`executeSync()`](#executesyncparams) but returns the raw value array result instead of the row objects.\n * @hidden Advanced use only.\n */\n public executeForRawResultSync(\n params: SQLiteBindParams\n ): SQLiteExecuteSyncResult>;\n /**\n * @hidden\n */\n public executeForRawResultSync(\n ...params: SQLiteVariadicBindParams\n ): SQLiteExecuteSyncResult>;\n public executeForRawResultSync(\n ...params: unknown[]\n ): SQLiteExecuteSyncResult> {\n const { lastInsertRowId, changes, firstRowValues } = this.nativeStatement.runSync(\n this.nativeDatabase,\n ...normalizeParams(...params)\n );\n return createSQLiteExecuteSyncResult>(\n this.nativeDatabase,\n this.nativeStatement,\n firstRowValues,\n {\n rawResult: true,\n lastInsertRowId,\n changes,\n }\n );\n }\n\n /**\n * Get the column names of the prepared statement.\n */\n public getColumnNamesSync(): string[] {\n return this.nativeStatement.getColumnNamesSync();\n }\n\n /**\n * Finalize the prepared statement. This will call the [`sqlite3_finalize()`](https://www.sqlite.org/c3ref/finalize.html) C function under the hood.\n *\n * Attempting to access a finalized statement will result in an error.\n *\n * > **Note:** While `expo-sqlite` will automatically finalize any orphaned prepared statements upon closing the database, it is considered best practice\n * > to manually finalize prepared statements as soon as they are no longer needed. This helps to prevent resource leaks.\n * > You can use the `try...finally` statement to ensure that prepared statements are finalized even if an error occurs.\n */\n public finalizeSync(): void {\n this.nativeStatement.finalizeSync(this.nativeDatabase);\n }\n\n //#endregion\n}\n\n/**\n * A result returned by [`SQLiteStatement.executeAsync()`](#executeasyncparams).\n *\n * @example\n * The result includes the [`lastInsertRowId`](https://www.sqlite.org/c3ref/last_insert_rowid.html) and [`changes`](https://www.sqlite.org/c3ref/changes.html) properties. You can get the information from the write operations.\n * ```ts\n * const statement = await db.prepareAsync('INSERT INTO test (value) VALUES (?)');\n * try {\n * const result = await statement.executeAsync(101);\n * console.log('lastInsertRowId:', result.lastInsertRowId);\n * console.log('changes:', result.changes);\n * } finally {\n * await statement.finalizeAsync();\n * }\n * ```\n *\n * @example\n * The result implements the [`AsyncIterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) interface, so you can use it in `for await...of` loops.\n * ```ts\n * const statement = await db.prepareAsync('SELECT value FROM test WHERE value > ?');\n * try {\n * const result = await statement.executeAsync<{ value: number }>(100);\n * for await (const row of result) {\n * console.log('row value:', row.value);\n * }\n * } finally {\n * await statement.finalizeAsync();\n * }\n * ```\n *\n * @example\n * If your write operations also return values, you can mix all of them together.\n * ```ts\n * const statement = await db.prepareAsync('INSERT INTO test (name, value) VALUES (?, ?) RETURNING name');\n * try {\n * const result = await statement.executeAsync<{ name: string }>('John Doe', 101);\n * console.log('lastInsertRowId:', result.lastInsertRowId);\n * console.log('changes:', result.changes);\n * for await (const row of result) {\n * console.log('name:', row.name);\n * }\n * } finally {\n * await statement.finalizeAsync();\n * }\n * ```\n */\nexport interface SQLiteExecuteAsyncResult extends AsyncIterableIterator {\n /**\n * The last inserted row ID. Returned from the [`sqlite3_last_insert_rowid()`](https://www.sqlite.org/c3ref/last_insert_rowid.html) function.\n */\n readonly lastInsertRowId: number;\n\n /**\n * The number of rows affected. Returned from the [`sqlite3_changes()`](https://www.sqlite.org/c3ref/changes.html) function.\n */\n readonly changes: number;\n\n /**\n * Get the first row of the result set. This requires the SQLite cursor to be in its initial state. If you have already retrieved rows from the result set, you need to reset the cursor first by calling [`resetAsync()`](#resetasync). Otherwise, an error will be thrown.\n */\n getFirstAsync(): Promise;\n\n /**\n * Get all rows of the result set. This requires the SQLite cursor to be in its initial state. If you have already retrieved rows from the result set, you need to reset the cursor first by calling [`resetAsync()`](#resetasync). Otherwise, an error will be thrown.\n */\n getAllAsync(): Promise;\n\n /**\n * Reset the prepared statement cursor. This will call the [`sqlite3_reset()`](https://www.sqlite.org/c3ref/reset.html) C function under the hood.\n */\n resetAsync(): Promise;\n}\n\n/**\n * A result returned by [`SQLiteStatement.executeSync()`](#executesyncparams).\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n\n * @example\n * The result includes the [`lastInsertRowId`](https://www.sqlite.org/c3ref/last_insert_rowid.html) and [`changes`](https://www.sqlite.org/c3ref/changes.html) properties. You can get the information from the write operations.\n * ```ts\n * const statement = db.prepareSync('INSERT INTO test (value) VALUES (?)');\n * try {\n * const result = statement.executeSync(101);\n * console.log('lastInsertRowId:', result.lastInsertRowId);\n * console.log('changes:', result.changes);\n * } finally {\n * statement.finalizeSync();\n * }\n * ```\n *\n * @example\n * The result implements the [`Iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) interface, so you can use it in `for...of` loops.\n * ```ts\n * const statement = db.prepareSync('SELECT value FROM test WHERE value > ?');\n * try {\n * const result = statement.executeSync<{ value: number }>(100);\n * for (const row of result) {\n * console.log('row value:', row.value);\n * }\n * } finally {\n * statement.finalizeSync();\n * }\n * ```\n *\n * @example\n * If your write operations also return values, you can mix all of them together.\n * ```ts\n * const statement = db.prepareSync('INSERT INTO test (name, value) VALUES (?, ?) RETURNING name');\n * try {\n * const result = statement.executeSync<{ name: string }>('John Doe', 101);\n * console.log('lastInsertRowId:', result.lastInsertRowId);\n * console.log('changes:', result.changes);\n * for (const row of result) {\n * console.log('name:', row.name);\n * }\n * } finally {\n * statement.finalizeSync();\n * }\n * ```\n */\nexport interface SQLiteExecuteSyncResult extends IterableIterator {\n /**\n * The last inserted row ID. Returned from the [`sqlite3_last_insert_rowid()`](https://www.sqlite.org/c3ref/last_insert_rowid.html) function.\n */\n readonly lastInsertRowId: number;\n\n /**\n * The number of rows affected. Returned from the [`sqlite3_changes()`](https://www.sqlite.org/c3ref/changes.html) function.\n */\n readonly changes: number;\n\n /**\n * Get the first row of the result set. This requires the SQLite cursor to be in its initial state. If you have already retrieved rows from the result set, you need to reset the cursor first by calling [`resetSync()`](#resetsync). Otherwise, an error will be thrown.\n */\n getFirstSync(): T | null;\n\n /**\n * Get all rows of the result set. This requires the SQLite cursor to be in its initial state. If you have already retrieved rows from the result set, you need to reset the cursor first by calling [`resetSync()`](#resetsync). Otherwise, an error will be thrown.\n */\n getAllSync(): T[];\n\n /**\n * Reset the prepared statement cursor. This will call the [`sqlite3_reset()`](https://www.sqlite.org/c3ref/reset.html) C function under the hood.\n */\n resetSync(): void;\n}\n\n//#region Internals for SQLiteExecuteAsyncResult and SQLiteExecuteSyncResult\n\ninterface SQLiteExecuteResultOptions {\n rawResult: boolean;\n lastInsertRowId: number;\n changes: number;\n}\n\n/**\n * Create the `SQLiteExecuteAsyncResult` instance.\n *\n * NOTE: Since Hermes does not support the `Symbol.asyncIterator` feature, we have to use an AsyncGenerator to implement the `AsyncIterableIterator` interface.\n * This is done by `Object.defineProperties` to add the properties to the AsyncGenerator.\n */\nasync function createSQLiteExecuteAsyncResult(\n database: SQLiteAnyDatabase,\n statement: NativeStatement,\n firstRowValues: SQLiteColumnValues | null,\n options: SQLiteExecuteResultOptions\n): Promise> {\n const instance = new SQLiteExecuteAsyncResultImpl(\n database,\n statement,\n firstRowValues,\n options\n );\n const generator = instance.generatorAsync();\n Object.defineProperties(generator, {\n lastInsertRowId: {\n value: options.lastInsertRowId,\n enumerable: true,\n writable: false,\n configurable: true,\n },\n changes: { value: options.changes, enumerable: true, writable: false, configurable: true },\n getFirstAsync: {\n value: instance.getFirstAsync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n getAllAsync: {\n value: instance.getAllAsync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n resetAsync: {\n value: instance.resetAsync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n });\n\n return generator as SQLiteExecuteAsyncResult;\n}\n\n/**\n * Create the `SQLiteExecuteSyncResult` instance.\n */\nfunction createSQLiteExecuteSyncResult(\n database: SQLiteAnyDatabase,\n statement: NativeStatement,\n firstRowValues: SQLiteColumnValues | null,\n options: SQLiteExecuteResultOptions\n): SQLiteExecuteSyncResult {\n const instance = new SQLiteExecuteSyncResultImpl(database, statement, firstRowValues, options);\n const generator = instance.generatorSync();\n Object.defineProperties(generator, {\n lastInsertRowId: {\n value: options.lastInsertRowId,\n enumerable: true,\n writable: false,\n configurable: true,\n },\n changes: { value: options.changes, enumerable: true, writable: false, configurable: true },\n getFirstSync: {\n value: instance.getFirstSync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n getAllSync: {\n value: instance.getAllSync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n resetSync: {\n value: instance.resetSync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n });\n\n return generator as SQLiteExecuteSyncResult;\n}\n\nclass SQLiteExecuteAsyncResultImpl {\n private columnNames: string[] | null = null;\n private isStepCalled = false;\n\n constructor(\n private readonly database: SQLiteAnyDatabase,\n private readonly statement: NativeStatement,\n private firstRowValues: SQLiteColumnValues | null,\n public readonly options: SQLiteExecuteResultOptions\n ) {}\n\n async getFirstAsync(): Promise {\n if (this.isStepCalled) {\n throw new Error(\n 'The SQLite cursor has been shifted and is unable to retrieve the first row without being reset. Invoke `resetAsync()` to reset the cursor first if you want to retrieve the first row.'\n );\n }\n this.isStepCalled = true;\n const columnNames = await this.getColumnNamesAsync();\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues != null) {\n return composeRowIfNeeded(this.options.rawResult, columnNames, firstRowValues);\n }\n const firstRow = await this.statement.stepAsync(this.database);\n return firstRow != null\n ? composeRowIfNeeded(this.options.rawResult, columnNames, firstRow)\n : null;\n }\n\n async getAllAsync(): Promise {\n if (this.isStepCalled) {\n throw new Error(\n 'The SQLite cursor has been shifted and is unable to retrieve all rows without being reset. Invoke `resetAsync()` to reset the cursor first if you want to retrieve all rows.'\n );\n }\n this.isStepCalled = true;\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues == null) {\n // If the first row is empty, this SQL query may be a write operation. We should not call `statement.getAllAsync()` to write again.\n return [];\n }\n const columnNames = await this.getColumnNamesAsync();\n const allRows = await this.statement.getAllAsync(this.database);\n if (firstRowValues != null && firstRowValues.length > 0) {\n return composeRowsIfNeeded(this.options.rawResult, columnNames, [\n firstRowValues,\n ...allRows,\n ]);\n }\n return composeRowsIfNeeded(this.options.rawResult, columnNames, allRows);\n }\n\n async *generatorAsync(): AsyncIterableIterator {\n this.isStepCalled = true;\n const columnNames = await this.getColumnNamesAsync();\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues != null) {\n yield composeRowIfNeeded(this.options.rawResult, columnNames, firstRowValues);\n }\n\n let result;\n do {\n result = await this.statement.stepAsync(this.database);\n if (result != null) {\n yield composeRowIfNeeded(this.options.rawResult, columnNames, result);\n }\n } while (result != null);\n }\n\n resetAsync(): Promise {\n const result = this.statement.resetAsync(this.database);\n this.isStepCalled = false;\n return result;\n }\n\n private popFirstRowValues(): SQLiteColumnValues | null {\n if (this.firstRowValues != null) {\n const firstRowValues = this.firstRowValues;\n this.firstRowValues = null;\n return firstRowValues.length > 0 ? firstRowValues : null;\n }\n return null;\n }\n\n private async getColumnNamesAsync(): Promise {\n if (this.columnNames == null) {\n this.columnNames = await this.statement.getColumnNamesAsync();\n }\n return this.columnNames;\n }\n}\n\nclass SQLiteExecuteSyncResultImpl {\n private columnNames: string[] | null = null;\n private isStepCalled = false;\n\n constructor(\n private readonly database: SQLiteAnyDatabase,\n private readonly statement: NativeStatement,\n private firstRowValues: SQLiteColumnValues | null,\n public readonly options: SQLiteExecuteResultOptions\n ) {}\n\n getFirstSync(): T | null {\n if (this.isStepCalled) {\n throw new Error(\n 'The SQLite cursor has been shifted and is unable to retrieve the first row without being reset. Invoke `resetSync()` to reset the cursor first if you want to retrieve the first row.'\n );\n }\n const columnNames = this.getColumnNamesSync();\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues != null) {\n return composeRowIfNeeded(this.options.rawResult, columnNames, firstRowValues);\n }\n const firstRow = this.statement.stepSync(this.database);\n return firstRow != null\n ? composeRowIfNeeded(this.options.rawResult, columnNames, firstRow)\n : null;\n }\n\n getAllSync(): T[] {\n if (this.isStepCalled) {\n throw new Error(\n 'The SQLite cursor has been shifted and is unable to retrieve all rows without being reset. Invoke `resetSync()` to reset the cursor first if you want to retrieve all rows.'\n );\n }\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues == null) {\n // If the first row is empty, this SQL query may be a write operation. We should not call `statement.getAllAsync()` to write again.\n return [];\n }\n const columnNames = this.getColumnNamesSync();\n const allRows = this.statement.getAllSync(this.database);\n if (firstRowValues != null && firstRowValues.length > 0) {\n return composeRowsIfNeeded(this.options.rawResult, columnNames, [\n firstRowValues,\n ...allRows,\n ]);\n }\n return composeRowsIfNeeded(this.options.rawResult, columnNames, allRows);\n }\n\n *generatorSync(): IterableIterator {\n const columnNames = this.getColumnNamesSync();\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues != null) {\n yield composeRowIfNeeded(this.options.rawResult, columnNames, firstRowValues);\n }\n let result;\n do {\n result = this.statement.stepSync(this.database);\n if (result != null) {\n yield composeRowIfNeeded(this.options.rawResult, columnNames, result);\n }\n } while (result != null);\n }\n\n resetSync(): void {\n const result = this.statement.resetSync(this.database);\n this.isStepCalled = false;\n return result;\n }\n\n private popFirstRowValues(): SQLiteColumnValues | null {\n if (this.firstRowValues != null) {\n const firstRowValues = this.firstRowValues;\n this.firstRowValues = null;\n return firstRowValues.length > 0 ? firstRowValues : null;\n }\n return null;\n }\n\n private getColumnNamesSync(): string[] {\n if (this.columnNames == null) {\n this.columnNames = this.statement.getColumnNamesSync();\n }\n return this.columnNames;\n }\n}\n\nfunction composeRowIfNeeded(\n rawResult: boolean,\n columnNames: SQLiteColumnNames,\n columnValues: SQLiteColumnValues\n): T {\n return rawResult\n ? (columnValues as T) // T would be a ValuesOf<> from caller\n : composeRow(columnNames, columnValues);\n}\n\nfunction composeRowsIfNeeded(\n rawResult: boolean,\n columnNames: SQLiteColumnNames,\n columnValuesList: SQLiteColumnValues[]\n): T[] {\n return rawResult\n ? (columnValuesList as T[]) // T[] would be a ValuesOf<>[] from caller\n : composeRows(columnNames, columnValuesList);\n}\n\n//#endregion\n"]} \ No newline at end of file +{"version":3,"file":"SQLiteStatement.js","sourceRoot":"","sources":["../src/SQLiteStatement.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeDatabase } from './NativeDatabase';\nimport {\n SQLiteBindParams,\n SQLiteBindValue,\n NativeStatement,\n SQLiteVariadicBindParams,\n type SQLiteAnyDatabase,\n type SQLiteColumnNames,\n type SQLiteColumnValues,\n type SQLiteRunResult,\n} from './NativeStatement';\nimport { composeRow, composeRows, normalizeParams } from './paramUtils';\n\nexport { SQLiteBindParams, SQLiteBindValue, SQLiteRunResult, SQLiteVariadicBindParams };\n\ntype ValuesOf = T[keyof T][];\n\n/**\n * A prepared statement returned by [`SQLiteDatabase.prepareAsync()`](#prepareasyncsource) or [`SQLiteDatabase.prepareSync()`](#preparesyncsource) that can be binded with parameters and executed.\n */\nexport class SQLiteStatement {\n constructor(\n private readonly nativeDatabase: NativeDatabase,\n private readonly nativeStatement: NativeStatement\n ) {}\n\n //#region Asynchronous API\n\n /**\n * Run the prepared statement and return the [`SQLiteExecuteAsyncResult`](#sqliteexecuteasyncresult) instance.\n * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`SQLiteBindValue`](#sqlitebindvalue) for more information about binding values.\n */\n public executeAsync(params: SQLiteBindParams): Promise>;\n /**\n * @hidden\n */\n public executeAsync(...params: SQLiteVariadicBindParams): Promise>;\n public async executeAsync(...params: unknown[]): Promise> {\n const { lastInsertRowId, changes, firstRowValues } = await this.nativeStatement.runAsync(\n this.nativeDatabase,\n ...normalizeParams(...params)\n );\n return createSQLiteExecuteAsyncResult(\n this.nativeDatabase,\n this.nativeStatement,\n firstRowValues,\n {\n rawResult: false,\n lastInsertRowId,\n changes,\n }\n );\n }\n\n /**\n * Similar to [`executeAsync()`](#executeasyncparams) but returns the raw value array result instead of the row objects.\n * @hidden Advanced use only.\n */\n public executeForRawResultAsync(\n params: SQLiteBindParams\n ): Promise>>;\n /**\n * @hidden\n */\n public executeForRawResultAsync(\n ...params: SQLiteVariadicBindParams\n ): Promise>>;\n public async executeForRawResultAsync(\n ...params: unknown[]\n ): Promise>> {\n const { lastInsertRowId, changes, firstRowValues } = await this.nativeStatement.runAsync(\n this.nativeDatabase,\n ...normalizeParams(...params)\n );\n return createSQLiteExecuteAsyncResult>(\n this.nativeDatabase,\n this.nativeStatement,\n firstRowValues,\n {\n rawResult: true,\n lastInsertRowId,\n changes,\n }\n );\n }\n\n /**\n * Get the column names of the prepared statement.\n */\n public getColumnNamesAsync(): Promise {\n return this.nativeStatement.getColumnNamesAsync();\n }\n\n /**\n * Finalize the prepared statement. This will call the [`sqlite3_finalize()`](https://www.sqlite.org/c3ref/finalize.html) C function under the hood.\n *\n * Attempting to access a finalized statement will result in an error.\n * > **Note:** While `expo-sqlite` will automatically finalize any orphaned prepared statements upon closing the database, it is considered best practice\n * > to manually finalize prepared statements as soon as they are no longer needed. This helps to prevent resource leaks.\n * > You can use the `try...finally` statement to ensure that prepared statements are finalized even if an error occurs.\n */\n public async finalizeAsync(): Promise {\n await this.nativeStatement.finalizeAsync(this.nativeDatabase);\n }\n\n //#endregion\n\n //#region Synchronous API\n\n /**\n * Run the prepared statement and return the [`SQLiteExecuteSyncResult`](#sqliteexecutesyncresult) instance.\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`SQLiteBindValue`](#sqlitebindvalue) for more information about binding values.\n */\n public executeSync(params: SQLiteBindParams): SQLiteExecuteSyncResult;\n /**\n * @hidden\n */\n public executeSync(...params: SQLiteVariadicBindParams): SQLiteExecuteSyncResult;\n public executeSync(...params: unknown[]): SQLiteExecuteSyncResult {\n const { lastInsertRowId, changes, firstRowValues } = this.nativeStatement.runSync(\n this.nativeDatabase,\n ...normalizeParams(...params)\n );\n return createSQLiteExecuteSyncResult(\n this.nativeDatabase,\n this.nativeStatement,\n firstRowValues,\n {\n rawResult: false,\n lastInsertRowId,\n changes,\n }\n );\n }\n\n /**\n * Similar to [`executeSync()`](#executesyncparams) but returns the raw value array result instead of the row objects.\n * @hidden Advanced use only.\n */\n public executeForRawResultSync(\n params: SQLiteBindParams\n ): SQLiteExecuteSyncResult>;\n /**\n * @hidden\n */\n public executeForRawResultSync(\n ...params: SQLiteVariadicBindParams\n ): SQLiteExecuteSyncResult>;\n public executeForRawResultSync(\n ...params: unknown[]\n ): SQLiteExecuteSyncResult> {\n const { lastInsertRowId, changes, firstRowValues } = this.nativeStatement.runSync(\n this.nativeDatabase,\n ...normalizeParams(...params)\n );\n return createSQLiteExecuteSyncResult>(\n this.nativeDatabase,\n this.nativeStatement,\n firstRowValues,\n {\n rawResult: true,\n lastInsertRowId,\n changes,\n }\n );\n }\n\n /**\n * Get the column names of the prepared statement.\n */\n public getColumnNamesSync(): string[] {\n return this.nativeStatement.getColumnNamesSync();\n }\n\n /**\n * Finalize the prepared statement. This will call the [`sqlite3_finalize()`](https://www.sqlite.org/c3ref/finalize.html) C function under the hood.\n *\n * Attempting to access a finalized statement will result in an error.\n *\n * > **Note:** While `expo-sqlite` will automatically finalize any orphaned prepared statements upon closing the database, it is considered best practice\n * > to manually finalize prepared statements as soon as they are no longer needed. This helps to prevent resource leaks.\n * > You can use the `try...finally` statement to ensure that prepared statements are finalized even if an error occurs.\n */\n public finalizeSync(): void {\n this.nativeStatement.finalizeSync(this.nativeDatabase);\n }\n\n //#endregion\n}\n\n/**\n * A result returned by [`SQLiteStatement.executeAsync()`](#executeasyncparams).\n *\n * @example\n * The result includes the [`lastInsertRowId`](https://www.sqlite.org/c3ref/last_insert_rowid.html) and [`changes`](https://www.sqlite.org/c3ref/changes.html) properties. You can get the information from the write operations.\n * ```ts\n * const statement = await db.prepareAsync('INSERT INTO test (value) VALUES (?)');\n * try {\n * const result = await statement.executeAsync(101);\n * console.log('lastInsertRowId:', result.lastInsertRowId);\n * console.log('changes:', result.changes);\n * } finally {\n * await statement.finalizeAsync();\n * }\n * ```\n *\n * @example\n * The result implements the [`AsyncIterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) interface, so you can use it in `for await...of` loops.\n * ```ts\n * const statement = await db.prepareAsync('SELECT value FROM test WHERE value > ?');\n * try {\n * const result = await statement.executeAsync<{ value: number }>(100);\n * for await (const row of result) {\n * console.log('row value:', row.value);\n * }\n * } finally {\n * await statement.finalizeAsync();\n * }\n * ```\n *\n * @example\n * If your write operations also return values, you can mix all of them together.\n * ```ts\n * const statement = await db.prepareAsync('INSERT INTO test (name, value) VALUES (?, ?) RETURNING name');\n * try {\n * const result = await statement.executeAsync<{ name: string }>('John Doe', 101);\n * console.log('lastInsertRowId:', result.lastInsertRowId);\n * console.log('changes:', result.changes);\n * for await (const row of result) {\n * console.log('name:', row.name);\n * }\n * } finally {\n * await statement.finalizeAsync();\n * }\n * ```\n */\nexport interface SQLiteExecuteAsyncResult extends AsyncIterableIterator {\n /**\n * The last inserted row ID. Returned from the [`sqlite3_last_insert_rowid()`](https://www.sqlite.org/c3ref/last_insert_rowid.html) function.\n */\n readonly lastInsertRowId: number;\n\n /**\n * The number of rows affected. Returned from the [`sqlite3_changes()`](https://www.sqlite.org/c3ref/changes.html) function.\n */\n readonly changes: number;\n\n /**\n * Get the first row of the result set. This requires the SQLite cursor to be in its initial state. If you have already retrieved rows from the result set, you need to reset the cursor first by calling [`resetAsync()`](#resetasync). Otherwise, an error will be thrown.\n */\n getFirstAsync(): Promise;\n\n /**\n * Get all rows of the result set. This requires the SQLite cursor to be in its initial state. If you have already retrieved rows from the result set, you need to reset the cursor first by calling [`resetAsync()`](#resetasync). Otherwise, an error will be thrown.\n */\n getAllAsync(): Promise;\n\n /**\n * Reset the prepared statement cursor. This will call the [`sqlite3_reset()`](https://www.sqlite.org/c3ref/reset.html) C function under the hood.\n */\n resetAsync(): Promise;\n}\n\n/**\n * A result returned by [`SQLiteStatement.executeSync()`](#executesyncparams).\n * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.\n\n * @example\n * The result includes the [`lastInsertRowId`](https://www.sqlite.org/c3ref/last_insert_rowid.html) and [`changes`](https://www.sqlite.org/c3ref/changes.html) properties. You can get the information from the write operations.\n * ```ts\n * const statement = db.prepareSync('INSERT INTO test (value) VALUES (?)');\n * try {\n * const result = statement.executeSync(101);\n * console.log('lastInsertRowId:', result.lastInsertRowId);\n * console.log('changes:', result.changes);\n * } finally {\n * statement.finalizeSync();\n * }\n * ```\n *\n * @example\n * The result implements the [`Iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) interface, so you can use it in `for...of` loops.\n * ```ts\n * const statement = db.prepareSync('SELECT value FROM test WHERE value > ?');\n * try {\n * const result = statement.executeSync<{ value: number }>(100);\n * for (const row of result) {\n * console.log('row value:', row.value);\n * }\n * } finally {\n * statement.finalizeSync();\n * }\n * ```\n *\n * @example\n * If your write operations also return values, you can mix all of them together.\n * ```ts\n * const statement = db.prepareSync('INSERT INTO test (name, value) VALUES (?, ?) RETURNING name');\n * try {\n * const result = statement.executeSync<{ name: string }>('John Doe', 101);\n * console.log('lastInsertRowId:', result.lastInsertRowId);\n * console.log('changes:', result.changes);\n * for (const row of result) {\n * console.log('name:', row.name);\n * }\n * } finally {\n * statement.finalizeSync();\n * }\n * ```\n */\nexport interface SQLiteExecuteSyncResult extends IterableIterator {\n /**\n * The last inserted row ID. Returned from the [`sqlite3_last_insert_rowid()`](https://www.sqlite.org/c3ref/last_insert_rowid.html) function.\n */\n readonly lastInsertRowId: number;\n\n /**\n * The number of rows affected. Returned from the [`sqlite3_changes()`](https://www.sqlite.org/c3ref/changes.html) function.\n */\n readonly changes: number;\n\n /**\n * Get the first row of the result set. This requires the SQLite cursor to be in its initial state. If you have already retrieved rows from the result set, you need to reset the cursor first by calling [`resetSync()`](#resetsync). Otherwise, an error will be thrown.\n */\n getFirstSync(): T | null;\n\n /**\n * Get all rows of the result set. This requires the SQLite cursor to be in its initial state. If you have already retrieved rows from the result set, you need to reset the cursor first by calling [`resetSync()`](#resetsync). Otherwise, an error will be thrown.\n */\n getAllSync(): T[];\n\n /**\n * Reset the prepared statement cursor. This will call the [`sqlite3_reset()`](https://www.sqlite.org/c3ref/reset.html) C function under the hood.\n */\n resetSync(): void;\n}\n\n//#region Internals for SQLiteExecuteAsyncResult and SQLiteExecuteSyncResult\n\ninterface SQLiteExecuteResultOptions {\n rawResult: boolean;\n lastInsertRowId: number;\n changes: number;\n}\n\n/**\n * Create the `SQLiteExecuteAsyncResult` instance.\n *\n * NOTE: Since Hermes does not support the `Symbol.asyncIterator` feature, we have to use an AsyncGenerator to implement the `AsyncIterableIterator` interface.\n * This is done by `Object.defineProperties` to add the properties to the AsyncGenerator.\n */\nasync function createSQLiteExecuteAsyncResult(\n database: SQLiteAnyDatabase,\n statement: NativeStatement,\n firstRowValues: SQLiteColumnValues | null,\n options: SQLiteExecuteResultOptions\n): Promise> {\n const instance = new SQLiteExecuteAsyncResultImpl(\n database,\n statement,\n firstRowValues ? processNativeRow(firstRowValues) : null,\n options\n );\n const generator = instance.generatorAsync();\n Object.defineProperties(generator, {\n lastInsertRowId: {\n value: options.lastInsertRowId,\n enumerable: true,\n writable: false,\n configurable: true,\n },\n changes: { value: options.changes, enumerable: true, writable: false, configurable: true },\n getFirstAsync: {\n value: instance.getFirstAsync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n getAllAsync: {\n value: instance.getAllAsync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n resetAsync: {\n value: instance.resetAsync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n });\n\n return generator as SQLiteExecuteAsyncResult;\n}\n\n/**\n * Create the `SQLiteExecuteSyncResult` instance.\n */\nfunction createSQLiteExecuteSyncResult(\n database: SQLiteAnyDatabase,\n statement: NativeStatement,\n firstRowValues: SQLiteColumnValues | null,\n options: SQLiteExecuteResultOptions\n): SQLiteExecuteSyncResult {\n const instance = new SQLiteExecuteSyncResultImpl(\n database,\n statement,\n firstRowValues ? processNativeRow(firstRowValues) : firstRowValues,\n options\n );\n const generator = instance.generatorSync();\n Object.defineProperties(generator, {\n lastInsertRowId: {\n value: options.lastInsertRowId,\n enumerable: true,\n writable: false,\n configurable: true,\n },\n changes: { value: options.changes, enumerable: true, writable: false, configurable: true },\n getFirstSync: {\n value: instance.getFirstSync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n getAllSync: {\n value: instance.getAllSync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n resetSync: {\n value: instance.resetSync.bind(instance),\n enumerable: true,\n writable: false,\n configurable: true,\n },\n });\n\n return generator as SQLiteExecuteSyncResult;\n}\n\nclass SQLiteExecuteAsyncResultImpl {\n private columnNames: string[] | null = null;\n private isStepCalled = false;\n\n constructor(\n private readonly database: SQLiteAnyDatabase,\n private readonly statement: NativeStatement,\n private firstRowValues: SQLiteColumnValues | null,\n public readonly options: SQLiteExecuteResultOptions\n ) {}\n\n async getFirstAsync(): Promise {\n if (this.isStepCalled) {\n throw new Error(\n 'The SQLite cursor has been shifted and is unable to retrieve the first row without being reset. Invoke `resetAsync()` to reset the cursor first if you want to retrieve the first row.'\n );\n }\n this.isStepCalled = true;\n const columnNames = await this.getColumnNamesAsync();\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues != null) {\n return composeRowIfNeeded(this.options.rawResult, columnNames, firstRowValues);\n }\n const firstRow = await this.statement.stepAsync(this.database);\n return firstRow != null\n ? composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(firstRow))\n : null;\n }\n\n async getAllAsync(): Promise {\n if (this.isStepCalled) {\n throw new Error(\n 'The SQLite cursor has been shifted and is unable to retrieve all rows without being reset. Invoke `resetAsync()` to reset the cursor first if you want to retrieve all rows.'\n );\n }\n this.isStepCalled = true;\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues == null) {\n // If the first row is empty, this SQL query may be a write operation. We should not call `statement.getAllAsync()` to write again.\n return [];\n }\n const columnNames = await this.getColumnNamesAsync();\n const nativeRows = await this.statement.getAllAsync(this.database);\n const allRows = processNativeRows(nativeRows);\n if (firstRowValues != null && firstRowValues.length > 0) {\n return composeRowsIfNeeded(this.options.rawResult, columnNames, [\n firstRowValues,\n ...allRows,\n ]);\n }\n return composeRowsIfNeeded(this.options.rawResult, columnNames, allRows);\n }\n\n async *generatorAsync(): AsyncIterableIterator {\n this.isStepCalled = true;\n const columnNames = await this.getColumnNamesAsync();\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues != null) {\n yield composeRowIfNeeded(this.options.rawResult, columnNames, firstRowValues);\n }\n\n let result;\n do {\n result = await this.statement.stepAsync(this.database);\n if (result != null) {\n yield composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(result));\n }\n } while (result != null);\n }\n\n resetAsync(): Promise {\n const result = this.statement.resetAsync(this.database);\n this.isStepCalled = false;\n return result;\n }\n\n private popFirstRowValues(): SQLiteColumnValues | null {\n if (this.firstRowValues != null) {\n const firstRowValues = this.firstRowValues;\n this.firstRowValues = null;\n return firstRowValues.length > 0 ? firstRowValues : null;\n }\n return null;\n }\n\n private async getColumnNamesAsync(): Promise {\n if (this.columnNames == null) {\n this.columnNames = await this.statement.getColumnNamesAsync();\n }\n return this.columnNames;\n }\n}\n\nclass SQLiteExecuteSyncResultImpl {\n private columnNames: string[] | null = null;\n private isStepCalled = false;\n\n constructor(\n private readonly database: SQLiteAnyDatabase,\n private readonly statement: NativeStatement,\n private firstRowValues: SQLiteColumnValues | null,\n public readonly options: SQLiteExecuteResultOptions\n ) {}\n\n getFirstSync(): T | null {\n if (this.isStepCalled) {\n throw new Error(\n 'The SQLite cursor has been shifted and is unable to retrieve the first row without being reset. Invoke `resetSync()` to reset the cursor first if you want to retrieve the first row.'\n );\n }\n const columnNames = this.getColumnNamesSync();\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues != null) {\n return composeRowIfNeeded(this.options.rawResult, columnNames, firstRowValues);\n }\n const firstRow = this.statement.stepSync(this.database);\n return firstRow != null\n ? composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(firstRow))\n : null;\n }\n\n getAllSync(): T[] {\n if (this.isStepCalled) {\n throw new Error(\n 'The SQLite cursor has been shifted and is unable to retrieve all rows without being reset. Invoke `resetSync()` to reset the cursor first if you want to retrieve all rows.'\n );\n }\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues == null) {\n // If the first row is empty, this SQL query may be a write operation. We should not call `statement.getAllAsync()` to write again.\n return [];\n }\n const columnNames = this.getColumnNamesSync();\n const nativeRows = this.statement.getAllSync(this.database);\n const allRows = processNativeRows(nativeRows);\n if (firstRowValues != null && firstRowValues.length > 0) {\n return composeRowsIfNeeded(this.options.rawResult, columnNames, [\n firstRowValues,\n ...allRows,\n ]);\n }\n return composeRowsIfNeeded(this.options.rawResult, columnNames, allRows);\n }\n\n *generatorSync(): IterableIterator {\n const columnNames = this.getColumnNamesSync();\n const firstRowValues = this.popFirstRowValues();\n if (firstRowValues != null) {\n yield composeRowIfNeeded(this.options.rawResult, columnNames, firstRowValues);\n }\n let result;\n do {\n result = this.statement.stepSync(this.database);\n if (result != null) {\n yield composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(result));\n }\n } while (result != null);\n }\n\n resetSync(): void {\n const result = this.statement.resetSync(this.database);\n this.isStepCalled = false;\n return result;\n }\n\n private popFirstRowValues(): SQLiteColumnValues | null {\n if (this.firstRowValues != null) {\n const firstRowValues = this.firstRowValues;\n this.firstRowValues = null;\n return firstRowValues.length > 0 ? firstRowValues : null;\n }\n return null;\n }\n\n private getColumnNamesSync(): string[] {\n if (this.columnNames == null) {\n this.columnNames = this.statement.getColumnNamesSync();\n }\n return this.columnNames;\n }\n}\n\nfunction composeRowIfNeeded(\n rawResult: boolean,\n columnNames: SQLiteColumnNames,\n columnValues: SQLiteColumnValues\n): T {\n return rawResult\n ? (columnValues as T) // T would be a ValuesOf<> from caller\n : composeRow(columnNames, columnValues);\n}\n\nfunction composeRowsIfNeeded(\n rawResult: boolean,\n columnNames: SQLiteColumnNames,\n columnValuesList: SQLiteColumnValues[]\n): T[] {\n return rawResult\n ? (columnValuesList as T[]) // T[] would be a ValuesOf<>[] from caller\n : composeRows(columnNames, columnValuesList);\n}\n\nfunction processNativeRow(nativeRow: SQLiteColumnValues): SQLiteColumnValues {\n return nativeRow?.map((column) =>\n column instanceof ArrayBuffer ? new Uint8Array(column) : column\n );\n}\n\nfunction processNativeRows(nativeRows: SQLiteColumnValues[]): SQLiteColumnValues[] {\n return nativeRows.map(processNativeRow);\n}\n\n//#endregion\n"]} \ No newline at end of file diff --git a/packages/expo-sqlite/src/SQLiteStatement.ts b/packages/expo-sqlite/src/SQLiteStatement.ts index bd384841761959..64ec1720d4f3f9 100644 --- a/packages/expo-sqlite/src/SQLiteStatement.ts +++ b/packages/expo-sqlite/src/SQLiteStatement.ts @@ -359,7 +359,7 @@ async function createSQLiteExecuteAsyncResult( const instance = new SQLiteExecuteAsyncResultImpl( database, statement, - firstRowValues, + firstRowValues ? processNativeRow(firstRowValues) : null, options ); const generator = instance.generatorAsync(); @@ -403,7 +403,12 @@ function createSQLiteExecuteSyncResult( firstRowValues: SQLiteColumnValues | null, options: SQLiteExecuteResultOptions ): SQLiteExecuteSyncResult { - const instance = new SQLiteExecuteSyncResultImpl(database, statement, firstRowValues, options); + const instance = new SQLiteExecuteSyncResultImpl( + database, + statement, + firstRowValues ? processNativeRow(firstRowValues) : firstRowValues, + options + ); const generator = instance.generatorSync(); Object.defineProperties(generator, { lastInsertRowId: { @@ -461,7 +466,7 @@ class SQLiteExecuteAsyncResultImpl { } const firstRow = await this.statement.stepAsync(this.database); return firstRow != null - ? composeRowIfNeeded(this.options.rawResult, columnNames, firstRow) + ? composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(firstRow)) : null; } @@ -478,7 +483,8 @@ class SQLiteExecuteAsyncResultImpl { return []; } const columnNames = await this.getColumnNamesAsync(); - const allRows = await this.statement.getAllAsync(this.database); + const nativeRows = await this.statement.getAllAsync(this.database); + const allRows = processNativeRows(nativeRows); if (firstRowValues != null && firstRowValues.length > 0) { return composeRowsIfNeeded(this.options.rawResult, columnNames, [ firstRowValues, @@ -500,7 +506,7 @@ class SQLiteExecuteAsyncResultImpl { do { result = await this.statement.stepAsync(this.database); if (result != null) { - yield composeRowIfNeeded(this.options.rawResult, columnNames, result); + yield composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(result)); } } while (result != null); } @@ -552,7 +558,7 @@ class SQLiteExecuteSyncResultImpl { } const firstRow = this.statement.stepSync(this.database); return firstRow != null - ? composeRowIfNeeded(this.options.rawResult, columnNames, firstRow) + ? composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(firstRow)) : null; } @@ -568,7 +574,8 @@ class SQLiteExecuteSyncResultImpl { return []; } const columnNames = this.getColumnNamesSync(); - const allRows = this.statement.getAllSync(this.database); + const nativeRows = this.statement.getAllSync(this.database); + const allRows = processNativeRows(nativeRows); if (firstRowValues != null && firstRowValues.length > 0) { return composeRowsIfNeeded(this.options.rawResult, columnNames, [ firstRowValues, @@ -588,7 +595,7 @@ class SQLiteExecuteSyncResultImpl { do { result = this.statement.stepSync(this.database); if (result != null) { - yield composeRowIfNeeded(this.options.rawResult, columnNames, result); + yield composeRowIfNeeded(this.options.rawResult, columnNames, processNativeRow(result)); } } while (result != null); } @@ -636,4 +643,14 @@ function composeRowsIfNeeded( : composeRows(columnNames, columnValuesList); } +function processNativeRow(nativeRow: SQLiteColumnValues): SQLiteColumnValues { + return nativeRow?.map((column) => + column instanceof ArrayBuffer ? new Uint8Array(column) : column + ); +} + +function processNativeRows(nativeRows: SQLiteColumnValues[]): SQLiteColumnValues[] { + return nativeRows.map(processNativeRow); +} + //#endregion From 60bbff0a4f44820d8028a66e462adb8dd0e90f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Klocek?= Date: Wed, 4 Mar 2026 09:32:37 +0100 Subject: [PATCH 5/6] [sqlite][ios] Use ArrayBuffers for returning blob columns (#42642) # Why Follow up #42640. Replace copying `Uint8Array` with `NativeArrayBuffer`. # How - Updated native code to return `ArrayBuffer` # Test Plan - iOS test suite - Manual testing # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/expo-sqlite/CHANGELOG.md | 1 + packages/expo-sqlite/ios/SQLiteModule.swift | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/expo-sqlite/CHANGELOG.md b/packages/expo-sqlite/CHANGELOG.md index 8a0c3700854210..9e31423b404ea8 100644 --- a/packages/expo-sqlite/CHANGELOG.md +++ b/packages/expo-sqlite/CHANGELOG.md @@ -13,6 +13,7 @@ - Session changesets now use native `ArrayBuffer`s. ([#42638](https://github.com/expo/expo/pull/42638) by [@barthap](https://github.com/barthap)) - Statement bind params now use native `ArrayBuffer`s for blob columns. ([#42639](https://github.com/expo/expo/pull/42639) by [@barthap](https://github.com/barthap)) - [Android] Returned blob columns now use native `ArrayBuffer`s. ([#42640](https://github.com/expo/expo/pull/42640) by [@barthap](https://github.com/barthap)) +- [iOS] Returned blob columns now use native `ArrayBuffer`s. ([#42642](https://github.com/expo/expo/pull/42642) by [@barthap](https://github.com/barthap)) ## 55.0.10 — 2026-02-25 diff --git a/packages/expo-sqlite/ios/SQLiteModule.swift b/packages/expo-sqlite/ios/SQLiteModule.swift index 029d5b626df496..d97c01e7392c01 100644 --- a/packages/expo-sqlite/ios/SQLiteModule.swift +++ b/packages/expo-sqlite/ios/SQLiteModule.swift @@ -615,10 +615,10 @@ public final class SQLiteModule: Module { return String(cString: text) case SQLITE_BLOB: guard let blob = exsqlite3_column_blob(instance, index) else { - return Data() + return NativeArrayBuffer.allocate(size: 0) } let size = exsqlite3_column_bytes(instance, index) - return Data(bytes: blob, count: Int(size)) + return NativeArrayBuffer.copy(of: blob, count: Int(size)) case SQLITE_NULL: return NSNull() default: From f7e1446643ecb852f89491f71a331dec4be7022a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kosmaty?= Date: Wed, 4 Mar 2026 09:43:08 +0100 Subject: [PATCH 6/6] [core][Android] Add state props (#42822) --- .../android/cmake/main.cmake | 3 +- .../modules/kotlin/views/ComposeViewProp.kt | 18 +++ .../src/fabric/FabricComponentsRegistry.cpp | 47 -------- .../src/fabric/FabricComponentsRegistry.h | 25 ----- .../android/src/main/cpp/JNIInjector.cpp | 4 +- .../AndroidExpoViewComponentDescriptor.cpp | 105 ++++++++++++++++++ .../AndroidExpoViewComponentDescriptor.h | 46 ++++++++ .../main/cpp/fabric/AndroidExpoViewProps.cpp | 22 ++++ .../main/cpp/fabric/AndroidExpoViewProps.h | 29 +++++ .../main/cpp/fabric/AndroidExpoViewState.cpp | 36 ++++++ .../main/cpp/fabric/AndroidExpoViewState.h | 36 ++++++ .../cpp/fabric/FabricComponentsRegistry.cpp | 97 ++++++++++++++++ .../cpp/fabric/FabricComponentsRegistry.h | 50 +++++++++ .../cpp/fabric/NativeStatePropsGetter.cpp | 32 ++++++ .../main/cpp/fabric/NativeStatePropsGetter.h | 25 +++++ .../react/FabricComponentsRegistry.kt | 39 ++++++- .../adapters/react/ModuleRegistryAdapter.java | 21 ++-- .../kotlin/KotlinInteropModuleRegistry.kt | 33 ++---- .../jni/fabric/NativeStatePropsGetter.kt | 9 ++ .../expo/modules/kotlin/views/AnyViewProp.kt | 4 + .../modules/kotlin/views/ConcreteViewProp.kt | 20 +++- .../kotlin/views/GroupViewManagerWrapper.kt | 11 +- .../kotlin/views/SimpleViewManagerWrapper.kt | 16 ++- .../kotlin/views/ViewDefinitionBuilder.kt | 24 ++-- .../views/ViewManagerWrapperDelegate.kt | 76 +++++++++++-- .../fabric/ExpoViewComponentDescriptor.cpp | 78 ------------- .../cpp/fabric/ExpoViewComponentDescriptor.h | 80 ++++++++++++- .../common/cpp/fabric/ExpoViewProps.cpp | 26 +++-- .../common/cpp/fabric/ExpoViewProps.h | 21 +++- .../common/cpp/fabric/ExpoViewShadowNode.cpp | 34 ------ .../common/cpp/fabric/ExpoViewShadowNode.h | 55 +++++++-- .../common/cpp/fabric/ExpoViewState.cpp | 37 ++++++ .../common/cpp/fabric/ExpoViewState.h | 62 ++--------- .../Views/SwiftUI/SwiftUIVirtualViewObjC.mm | 10 +- .../ios/Fabric/ExpoFabricViewObjC.mm | 10 +- 35 files changed, 899 insertions(+), 342 deletions(-) delete mode 100644 packages/expo-modules-core/android/src/fabric/FabricComponentsRegistry.cpp delete mode 100644 packages/expo-modules-core/android/src/fabric/FabricComponentsRegistry.h create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewComponentDescriptor.cpp create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewComponentDescriptor.h create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewProps.cpp create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewProps.h create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewState.cpp create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewState.h create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/FabricComponentsRegistry.cpp create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/FabricComponentsRegistry.h create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/NativeStatePropsGetter.cpp create mode 100644 packages/expo-modules-core/android/src/main/cpp/fabric/NativeStatePropsGetter.h create mode 100644 packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/jni/fabric/NativeStatePropsGetter.kt delete mode 100644 packages/expo-modules-core/common/cpp/fabric/ExpoViewComponentDescriptor.cpp create mode 100644 packages/expo-modules-core/common/cpp/fabric/ExpoViewState.cpp diff --git a/packages/expo-modules-core/android/cmake/main.cmake b/packages/expo-modules-core/android/cmake/main.cmake index c79b8c62a57b0d..2241c02c0e1bff 100644 --- a/packages/expo-modules-core/android/cmake/main.cmake +++ b/packages/expo-modules-core/android/cmake/main.cmake @@ -15,10 +15,9 @@ file( "${main_dir}/decorators/*.cpp" "${main_dir}/installers/*.cpp" "${main_dir}/worklets/*.cpp" + "${main_dir}/fabric/*.cpp" ) -file(GLOB fabric_andorid_sources "${ANDROID_SRC_DIR}/fabric/*.cpp") - add_library( expo-modules-core SHARED diff --git a/packages/expo-modules-core/android/src/compose/expo/modules/kotlin/views/ComposeViewProp.kt b/packages/expo-modules-core/android/src/compose/expo/modules/kotlin/views/ComposeViewProp.kt index 739e7abe46f374..b272e3f8b04004 100644 --- a/packages/expo-modules-core/android/src/compose/expo/modules/kotlin/views/ComposeViewProp.kt +++ b/packages/expo-modules-core/android/src/compose/expo/modules/kotlin/views/ComposeViewProp.kt @@ -17,9 +17,19 @@ class ComposeViewProp( anyType: AnyType, val property: KProperty1<*, *> ) : AnyViewProp(name, anyType) { + private var _isStateProp = false @Suppress("UNCHECKED_CAST") override fun set(prop: Dynamic, onView: View, appContext: AppContext?) { + setPropDirectly(prop, onView, appContext) + } + + override fun set(prop: Any?, onView: View, appContext: AppContext?) { + setPropDirectly(prop, onView, appContext) + } + + @Suppress("UNCHECKED_CAST") + private fun setPropDirectly(prop: Any?, onView: View, appContext: AppContext?) { exceptionDecorator({ PropSetException(name, onView::class, it) }) { @@ -50,5 +60,13 @@ class ComposeViewProp( } } + fun asStateProp(): ComposeViewProp { + _isStateProp = true + return this + } + override val isNullable: Boolean = anyType.kType.isMarkedNullable + + override val isStateProp: Boolean + get() = _isStateProp } diff --git a/packages/expo-modules-core/android/src/fabric/FabricComponentsRegistry.cpp b/packages/expo-modules-core/android/src/fabric/FabricComponentsRegistry.cpp deleted file mode 100644 index 42b71560d91221..00000000000000 --- a/packages/expo-modules-core/android/src/fabric/FabricComponentsRegistry.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2018-present 650 Industries. All rights reserved. - -#include "FabricComponentsRegistry.h" - -#include -#include - -#include "ExpoViewComponentDescriptor.h" - -namespace jni = facebook::jni; -namespace react = facebook::react; - -namespace expo { - -// static -void FabricComponentsRegistry::registerNatives() { - registerHybrid({ - makeNativeMethod("initHybrid", FabricComponentsRegistry::initHybrid), - makeNativeMethod("registerComponentsRegistry", FabricComponentsRegistry::registerComponentsRegistry), - }); -} - -// static -jni::local_ref -FabricComponentsRegistry::initHybrid(jni::alias_ref jThis) { - return makeCxxInstance(); -} - -void FabricComponentsRegistry::registerComponentsRegistry( - jni::alias_ref> componentNames) { - // Inject the component to the CoreComponentsRegistry because we don't want to touch the MainApplicationReactNativeHost - auto providerRegistry = react::CoreComponentsRegistry::sharedProviderRegistry(); - - size_t size = componentNames->size(); - for (size_t i = 0; i < size; ++i) { - auto flavor = std::make_shared(componentNames->getElement(i)->toStdString()); - auto componentName = react::ComponentName{flavor->c_str()}; - providerRegistry->add(react::ComponentDescriptorProvider { - reinterpret_cast(componentName), - componentName, - flavor, - &facebook::react::concreteComponentDescriptorConstructor - }); - } -} - -} // namespace expo diff --git a/packages/expo-modules-core/android/src/fabric/FabricComponentsRegistry.h b/packages/expo-modules-core/android/src/fabric/FabricComponentsRegistry.h deleted file mode 100644 index aaee960b0023ee..00000000000000 --- a/packages/expo-modules-core/android/src/fabric/FabricComponentsRegistry.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018-present 650 Industries. All rights reserved. - -#pragma once - -#include - -namespace expo { - -class FabricComponentsRegistry : public facebook::jni::HybridClass { -public: - static auto constexpr - kJavaDescriptor = "Lexpo/modules/adapters/react/FabricComponentsRegistry;"; - - static void registerNatives(); - - FabricComponentsRegistry() {}; - - void registerComponentsRegistry( - facebook::jni::alias_ref> componentNames); - -private: - static facebook::jni::local_ref initHybrid(facebook::jni::alias_ref jThis); -}; - -} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/JNIInjector.cpp b/packages/expo-modules-core/android/src/main/cpp/JNIInjector.cpp index 4f7fc768fcc2db..805070d6f7afec 100644 --- a/packages/expo-modules-core/android/src/main/cpp/JNIInjector.cpp +++ b/packages/expo-modules-core/android/src/main/cpp/JNIInjector.cpp @@ -21,7 +21,8 @@ #include "worklets/WorkletNativeRuntime.h" #if RN_FABRIC_ENABLED -#include "FabricComponentsRegistry.h" +#include "fabric/FabricComponentsRegistry.h" +#include "fabric/NativeStatePropsGetter.h" #endif #include @@ -62,6 +63,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { #if RN_FABRIC_ENABLED expo::FabricComponentsRegistry::registerNatives(); + expo::NativeStatePropsGetter::registerNatives(); #endif }); } diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewComponentDescriptor.cpp b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewComponentDescriptor.cpp new file mode 100644 index 00000000000000..071db6108d8ae9 --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewComponentDescriptor.cpp @@ -0,0 +1,105 @@ +#include "AndroidExpoViewComponentDescriptor.h" + +namespace expo { + +// TODO(@lukmccall): Ask for public API to access RawProps internals +struct RawPropsAccessor { + react::RawPropsParser *parser_{nullptr}; + react::RawProps::Mode mode_; + jsi::Runtime *runtime_{}; + jsi::Value value_; +}; + +void AndroidExpoViewComponentDescriptor::setStateProps( + const std::unordered_map< + std::string, + std::shared_ptr + > &stateProps +) { + stateProps_ = stateProps; +} + +react::Props::Shared AndroidExpoViewComponentDescriptor::cloneProps( + const react::PropsParserContext &context, + const react::Props::Shared &props, + react::RawProps rawProps +) const { + if (!props && rawProps.isEmpty()) { + return ExpoViewShadowNode::defaultSharedProps(); + } + + rawProps.parse(rawPropsParser_); + + auto shadowNodeProps = std::make_shared( + context, + props ? dynamic_cast(*props) + : *ExpoViewShadowNode::defaultSharedProps(), + rawProps, + filterObjectKeys_ + ); + + // TODO(@lukmccall): We probably can remove this loop + if (react::ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) { +#ifdef RN_SERIALIZABLE_STATE + const auto &dynamic = shadowNodeProps->rawProps; +#else + const auto &dynamic = static_cast(rawProps); +#endif + for (const auto &pair: dynamic.items()) { + const auto &name = pair.first.getString(); + shadowNodeProps->setProp( + context, + RAW_PROPS_KEY_HASH(name), + name.c_str(), + react::RawValue(pair.second) + ); + } + } + + if (!stateProps_.empty()) { + JNIEnv *env = jni::Environment::current(); + const auto &rawPropsAccessor = *((RawPropsAccessor *) &rawProps); + + jsi::Runtime &runtime = *rawPropsAccessor.runtime_; + const auto jsiProps = rawPropsAccessor.value_.asObject(runtime); + + for (const auto &statePropPair: stateProps_) { + const auto &[propName, converter] = statePropPair; + + const auto jsPropName = jsi::String::createFromUtf8(runtime, propName); + if (!jsiProps.hasProperty(runtime, jsPropName)) { + continue; // Property does not exist on the JS object + } + + const auto value = jsiProps.getProperty(runtime, jsPropName); + + if (shadowNodeProps->statePropsDiff == nullptr) { + shadowNodeProps->statePropsDiff = jni::make_global( + jni::JHashMap::create() + ); + } + + jobject jConvertedValue = converter->convert(runtime, env, value); + shadowNodeProps->statePropsDiff->put( + jni::make_jstring(propName), + jConvertedValue + ); + } + } + + return shadowNodeProps; +} + +void AndroidExpoViewComponentDescriptor::adopt(react::ShadowNode &shadowNode) const { + react_native_assert(dynamic_cast(&shadowNode)); + + Base::adopt(shadowNode); + + const auto snode = dynamic_cast(&shadowNode); + auto &props = *std::static_pointer_cast( + snode->getProps()); + snode->getStateData().statePropsDiff = props.statePropsDiff; + props.statePropsDiff = nullptr; +} + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewComponentDescriptor.h b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewComponentDescriptor.h new file mode 100644 index 00000000000000..18399ace4784fc --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewComponentDescriptor.h @@ -0,0 +1,46 @@ +#pragma once + +#include "ExpoViewComponentDescriptor.h" +#include "AndroidExpoViewProps.h" +#include "AndroidExpoViewState.h" +#include "../types/FrontendConverter.h" + +namespace react = facebook::react; + +namespace expo { + +class AndroidExpoViewComponentDescriptor + : public ExpoViewComponentDescriptor> { +public: + using Base = ExpoViewComponentDescriptor>; + using ExpoShadowNode = ExpoViewShadowNode; + + using Base::ExpoViewComponentDescriptor; + + void setStateProps( + const std::unordered_map< + std::string, + std::shared_ptr + > &stateProps + ); + + react::Props::Shared cloneProps( + const react::PropsParserContext &context, + const react::Props::Shared &props, + react::RawProps rawProps + ) const override; + + void adopt(react::ShadowNode &shadowNode) const override; + +private: + std::unordered_map< + std::string, + std::shared_ptr + > stateProps_; + + std::function filterObjectKeys_ = [this](const std::string &key) { + return stateProps_.find(key) != stateProps_.end(); + }; +}; + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewProps.cpp b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewProps.cpp new file mode 100644 index 00000000000000..2179826d5dea33 --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewProps.cpp @@ -0,0 +1,22 @@ +#include "AndroidExpoViewProps.h" + +namespace expo { + +AndroidExpoViewProps::AndroidExpoViewProps( + const facebook::react::PropsParserContext &context, + const AndroidExpoViewProps &sourceProps, + const facebook::react::RawProps &rawProps, + const std::function &filterObjectKeys +) : ExpoViewProps(context, sourceProps, rawProps, filterObjectKeys), + statePropsDiff(nullptr) { +} + +AndroidExpoViewProps::~AndroidExpoViewProps() { + if (statePropsDiff != nullptr) { + jni::ThreadScope::WithClassLoader([this] { + jni::Environment::current()->DeleteGlobalRef(statePropsDiff.release()); + }); + } +} + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewProps.h b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewProps.h new file mode 100644 index 00000000000000..cef2eaf46c4260 --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewProps.h @@ -0,0 +1,29 @@ +#pragma once + +#include "ExpoViewProps.h" + +#include +#include + +namespace react = facebook::react; +namespace jni = facebook::jni; + +namespace expo { + +class AndroidExpoViewProps : public ExpoViewProps { +public: + AndroidExpoViewProps() = default; + + AndroidExpoViewProps( + const facebook::react::PropsParserContext &context, + const AndroidExpoViewProps &sourceProps, + const facebook::react::RawProps &rawProps, + const std::function &filterObjectKeys = nullptr + ); + + ~AndroidExpoViewProps() override; + + mutable jni::global_ref > statePropsDiff; +}; + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewState.cpp b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewState.cpp new file mode 100644 index 00000000000000..f8239a746ce042 --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewState.cpp @@ -0,0 +1,36 @@ +#include "AndroidExpoViewState.h" + +namespace expo { + +AndroidExpoViewState::AndroidExpoViewState( + AndroidExpoViewState const &previousState, + folly::dynamic data +) { + this->_width = isNonnullProperty(data, "width") ? (float) data["width"].getDouble() + : std::numeric_limits::quiet_NaN(); + this->_height = isNonnullProperty(data, "height") ? (float) data["height"].getDouble() + : std::numeric_limits::quiet_NaN(); + this->_styleWidth = isNonnullProperty(data, "styleWidth") ? (float) data["styleWidth"].getDouble() + : std::numeric_limits::quiet_NaN(); + this->_styleHeight = isNonnullProperty(data, "styleHeight") + ? (float) data["styleHeight"].getDouble() + : std::numeric_limits::quiet_NaN(); +} + +folly::dynamic AndroidExpoViewState::getDynamic() const { + return {}; +} + +facebook::react::MapBuffer AndroidExpoViewState::getMapBuffer() const { + return facebook::react::MapBufferBuilder::EMPTY(); +} + +AndroidExpoViewState::~AndroidExpoViewState() { + if (statePropsDiff != nullptr) { + jni::ThreadScope::WithClassLoader([this] { + jni::Environment::current()->DeleteGlobalRef(statePropsDiff.release()); + }); + } +} + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewState.h b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewState.h new file mode 100644 index 00000000000000..231ec49f33c8d8 --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/AndroidExpoViewState.h @@ -0,0 +1,36 @@ +#include "ExpoViewState.h" + +#include "AndroidExpoViewProps.h" +#include +#include +#include +#include + +namespace react = facebook::react; +namespace jni = facebook::jni; + +namespace expo { + +class AndroidExpoViewState : public ExpoViewState { +public: + using ExpoViewState::ExpoViewState; + + AndroidExpoViewState( + AndroidExpoViewState const &previousState, + folly::dynamic data + ); + + ~AndroidExpoViewState() override; + + folly::dynamic getDynamic() const; + + facebook::react::MapBuffer getMapBuffer() const; + + static inline bool isNonnullProperty(const folly::dynamic &value, const std::string &name) { + return value.count(name) && !value[name].isNull(); + } + + mutable jni::global_ref> statePropsDiff; +}; + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/FabricComponentsRegistry.cpp b/packages/expo-modules-core/android/src/main/cpp/fabric/FabricComponentsRegistry.cpp new file mode 100644 index 00000000000000..3ed49fd392dfbc --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/FabricComponentsRegistry.cpp @@ -0,0 +1,97 @@ +// Copyright 2018-present 650 Industries. All rights reserved. + +#include "FabricComponentsRegistry.h" +#include "../types/FrontendConverterProvider.h" + +namespace jni = facebook::jni; +namespace react = facebook::react; + +namespace expo { + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wextern-initializer" +// Clang think that we don't need to initialize the extern variable here, but we do. +extern StatePropMapType statePropMap = {}; +#pragma clang diagnostic pop + +AndroidExpoViewComponentDescriptor::Unique concreteExpoComponentDescriptorConstructor( + const react::ComponentDescriptorParameters ¶meters +) { + auto descriptor = std::make_unique( + parameters, + react::RawPropsParser(/*useRawPropsJsiValue=*/true) + ); + + if (statePropMap.contains(std::static_pointer_cast(parameters.flavor))) { + descriptor->setStateProps( + statePropMap.at( + std::static_pointer_cast(parameters.flavor) + ) + ); + } + + return descriptor; +} + +// static +void FabricComponentsRegistry::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", FabricComponentsRegistry::initHybrid), + makeNativeMethod("registerComponentsRegistry", + FabricComponentsRegistry::registerComponentsRegistry), + }); +} + +// static +jni::local_ref FabricComponentsRegistry::initHybrid( + jni::alias_ref jThis +) { + return makeCxxInstance(); +} + +void FabricComponentsRegistry::registerComponentsRegistry( + jni::alias_ref> componentNames, + jni::alias_ref>> statePropNames, + jni::alias_ref>> statePropTypes +) { + // Inject the component to the CoreComponentsRegistry because we don't want to touch the MainApplicationReactNativeHost + auto providerRegistry = react::CoreComponentsRegistry::sharedProviderRegistry(); + + size_t size = componentNames->size(); + assert(size == statePropNames->size()); + assert(size == statePropTypes->size()); + + auto frontendConverterProvider = FrontendConverterProvider::instance(); + + for (size_t i = 0; i < size; ++i) { + auto flavor = std::make_shared(componentNames->getElement(i)->toStdString()); + auto componentName = react::ComponentName{flavor->c_str()}; + + std::unordered_map> propMap; + + auto propNames = statePropNames->getElement(i); + auto propTypes = statePropTypes->getElement(i); + assert(propNames->size() == propTypes->size()); + + for (size_t j = 0; j < propNames->size(); ++j) { + auto propName = propNames->getElement(j)->toStdString(); + auto propType = propTypes->getElement(j); + auto converter = frontendConverterProvider->obtainConverter(propType); + propMap.emplace(propName, converter); + } + + statePropMap.insert_or_assign( + flavor, + propMap + ); + + providerRegistry->add(react::ComponentDescriptorProvider{ + reinterpret_cast(componentName), + componentName, + flavor, + &concreteExpoComponentDescriptorConstructor + }); + } +} + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/FabricComponentsRegistry.h b/packages/expo-modules-core/android/src/main/cpp/fabric/FabricComponentsRegistry.h new file mode 100644 index 00000000000000..e7ff0180409583 --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/FabricComponentsRegistry.h @@ -0,0 +1,50 @@ +// Copyright 2018-present 650 Industries. All rights reserved. + +#pragma once + +#include +#include +#include + +#include "../types/ExpectedType.h" +#include "../types/FrontendConverter.h" +#include "AndroidExpoViewComponentDescriptor.h" + +namespace jni = facebook::jni; +namespace react = facebook::react; + +namespace expo { + +typedef std::unordered_map< + AndroidExpoViewComponentDescriptor::Flavor, + std::unordered_map> +> StatePropMapType; + +extern StatePropMapType statePropMap; + +AndroidExpoViewComponentDescriptor::Unique concreteExpoComponentDescriptorConstructor( + const react::ComponentDescriptorParameters ¶meters +); + +class FabricComponentsRegistry : public jni::HybridClass { +public: + static auto constexpr + kJavaDescriptor = "Lexpo/modules/adapters/react/FabricComponentsRegistry;"; + + static void registerNatives(); + + FabricComponentsRegistry() {}; + + void registerComponentsRegistry( + jni::alias_ref> componentNames, + jni::alias_ref>> statePropNames, + jni::alias_ref>> statePropTypes + ); + +private: + static jni::local_ref initHybrid( + jni::alias_ref jThis + ); +}; + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/NativeStatePropsGetter.cpp b/packages/expo-modules-core/android/src/main/cpp/fabric/NativeStatePropsGetter.cpp new file mode 100644 index 00000000000000..79d15d628125ee --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/NativeStatePropsGetter.cpp @@ -0,0 +1,32 @@ +#include "NativeStatePropsGetter.h" +#include "AndroidExpoViewState.h" + +#include + +namespace expo { + +void NativeStatePropsGetter::registerNatives() { + javaClassLocal()->registerNatives({ + makeNativeMethod("getStateProps", NativeStatePropsGetter::getStateProps), + }); +} + +jni::local_ref> NativeStatePropsGetter::getStateProps( + jni::alias_ref self, + jni::alias_ref stateWrapper +) { + auto stateWrapperImpl = jni::alias_ref{ + static_cast(stateWrapper.get()) + }; + + const auto &nativeStateWrapper = std::dynamic_pointer_cast>( + stateWrapperImpl->cthis()->getState() + ); + const auto &nativeState = nativeStateWrapper->getData(); + + const auto localNativeState = jni::make_local(nativeState.statePropsDiff); + nativeState.statePropsDiff = nullptr; + return localNativeState; +} + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/cpp/fabric/NativeStatePropsGetter.h b/packages/expo-modules-core/android/src/main/cpp/fabric/NativeStatePropsGetter.h new file mode 100644 index 00000000000000..e4ab489fea050b --- /dev/null +++ b/packages/expo-modules-core/android/src/main/cpp/fabric/NativeStatePropsGetter.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace jni = facebook::jni; +namespace react = facebook::react; + +namespace expo { + +class NativeStatePropsGetter : public jni::JavaClass { +public: + static auto constexpr kJavaDescriptor = "Lexpo/modules/kotlin/jni/fabric/NativeStatePropsGetter;"; + static auto constexpr TAG = "NativeStatePropsGetter"; + + static void registerNatives(); + + static jni::local_ref> getStateProps( + jni::alias_ref self, + jni::alias_ref stateWrapper + ); +}; + +} // namespace expo diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/FabricComponentsRegistry.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/FabricComponentsRegistry.kt index b6567d3bb38328..2d3bdfa0081475 100644 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/FabricComponentsRegistry.kt +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/FabricComponentsRegistry.kt @@ -3,24 +3,51 @@ package expo.modules.adapters.react import com.facebook.jni.HybridData -import com.facebook.react.uimanager.ViewManager import com.facebook.soloader.SoLoader import expo.modules.core.interfaces.DoNotStrip +import expo.modules.kotlin.jni.ExpectedType +import expo.modules.kotlin.views.ViewManagerWrapperDelegate @Suppress("KotlinJniMissingFunction") @DoNotStrip -class FabricComponentsRegistry(viewManagerList: List>) { - private val componentNames: List = viewManagerList.map { it.name } - +class FabricComponentsRegistry(viewDelegates: List) { @DoNotStrip private val mHybridData = initHybrid() init { - registerComponentsRegistry(componentNames.toTypedArray()) + val componentNames = Array(viewDelegates.size) { i -> + viewDelegates[i].viewManagerName + } + + val stateProps = viewDelegates.map { + it.props.filter { (_, prop) -> prop.isStateProp }.values + } + + val statePropNames = Array(componentNames.size) { i -> + Array(stateProps[i].size) { j -> + stateProps[i].elementAt(j).name + } + } + + val statePropsType = Array(componentNames.size) { i -> + Array(stateProps[i].size) { j -> + stateProps[i].elementAt(j).type.getCppRequiredTypes() + } + } + + registerComponentsRegistry( + componentNames, + statePropNames, + statePropsType + ) } private external fun initHybrid(): HybridData - private external fun registerComponentsRegistry(componentNames: Array) + private external fun registerComponentsRegistry( + componentNames: Array, + statePropNames: Array>, + statePropTypes: Array> + ) @Throws(Throwable::class) protected fun finalize() { diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/ModuleRegistryAdapter.java b/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/ModuleRegistryAdapter.java index e44d27d62994d2..10af42b2bcbecc 100644 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/ModuleRegistryAdapter.java +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/ModuleRegistryAdapter.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import androidx.annotation.Nullable; @@ -22,7 +23,7 @@ import expo.modules.kotlin.ExpoBridgeModule; import expo.modules.kotlin.KotlinInteropModuleRegistry; import expo.modules.kotlin.ModulesProvider; -import expo.modules.kotlin.views.ViewWrapperDelegateHolder; +import expo.modules.kotlin.views.ViewManagerWrapperDelegate; /** * An adapter over {@link ModuleRegistry}, compatible with React (implementing {@link ReactPackage}). @@ -43,7 +44,7 @@ private void setModulesProxy(@Nullable NativeModulesProxy newProxy) { } // We need to save all view holders to update them when the new kotlin module registry will be created. - private List mWrapperDelegateHolders = null; + private List mWrapperDelegates = null; private FabricComponentsRegistry mFabricComponentsRegistry = null; public ModuleRegistryAdapter(List packageList) { @@ -69,9 +70,9 @@ public List createNativeModules(ReactApplicationContext reactConte } List nativeModules = getNativeModulesFromModuleRegistry(reactContext, moduleRegistry, null); - if (mWrapperDelegateHolders != null) { + if (mWrapperDelegates != null) { KotlinInteropModuleRegistry kotlinInteropModuleRegistry = proxy.getKotlinInteropModuleRegistry(); - kotlinInteropModuleRegistry.updateModuleHoldersInViewManagers(mWrapperDelegateHolders); + kotlinInteropModuleRegistry.updateModuleHoldersInViewDelegates(mWrapperDelegates); } return nativeModules; @@ -108,13 +109,17 @@ public List createViewManagers(ReactApplicationContext reactContext NativeModulesProxy modulesProxy = Objects.requireNonNull(getOrCreateNativeModulesProxy(reactContext, null)); KotlinInteropModuleRegistry kotlinInteropModuleRegistry = modulesProxy.getKotlinInteropModuleRegistry(); - List> kViewManager = kotlinInteropModuleRegistry.exportViewManagers(); + List kViewManagerDelegates = kotlinInteropModuleRegistry.exportViewManagerDelegates(); // Saves all holders that needs to be in sync with module registry - mWrapperDelegateHolders = kotlinInteropModuleRegistry.extractViewManagersDelegateHolders(kViewManager); - viewManagerList.addAll(kViewManager); + mWrapperDelegates = kViewManagerDelegates; + List> viewManagers = kViewManagerDelegates + .stream() + .map(ViewManagerWrapperDelegate::toRNViewManager) + .collect(Collectors.toList()); + viewManagerList.addAll(viewManagers); if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { // Intentionally to only add Sweet API view managers for Fabric support - mFabricComponentsRegistry = new FabricComponentsRegistry(kViewManager); + mFabricComponentsRegistry = new FabricComponentsRegistry(kViewManagerDelegates); } return viewManagerList; diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt index 86255d1aa9b640..4c4862e838f6c3 100644 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt @@ -2,17 +2,12 @@ package expo.modules.kotlin import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray -import com.facebook.react.uimanager.ViewManager import expo.modules.adapters.react.NativeModulesProxy import expo.modules.kotlin.exception.CodedException import expo.modules.kotlin.exception.UnexpectedException import expo.modules.kotlin.modules.DEFAULT_MODULE_VIEW import expo.modules.kotlin.tracing.trace -import expo.modules.kotlin.views.GroupViewManagerWrapper -import expo.modules.kotlin.views.SimpleViewManagerWrapper -import expo.modules.kotlin.views.ViewManagerType import expo.modules.kotlin.views.ViewManagerWrapperDelegate -import expo.modules.kotlin.views.ViewWrapperDelegateHolder import java.lang.ref.WeakReference class KotlinInteropModuleRegistry( @@ -44,16 +39,12 @@ class KotlinInteropModuleRegistry( } } - fun exportViewManagers(): List> = - trace("KotlinInteropModuleRegistry.exportViewManagers") { + fun exportViewManagerDelegates(): List = + trace("KotlinInteropModuleRegistry.exportViewManagerDelegates") { registry .flatMap { module -> module.definition.viewManagerDefinitions.map { (name, definition) -> - val wrapperDelegate = ViewManagerWrapperDelegate(module, definition, if (name == DEFAULT_MODULE_VIEW) module.name else null) - when (definition.getViewManagerType()) { - ViewManagerType.SIMPLE -> SimpleViewManagerWrapper(wrapperDelegate) - ViewManagerType.GROUP -> GroupViewManagerWrapper(wrapperDelegate) - } + ViewManagerWrapperDelegate(module, definition, if (name == DEFAULT_MODULE_VIEW) module.name else null) } } } @@ -74,11 +65,6 @@ class KotlinInteropModuleRegistry( return@trace result } - fun extractViewManagersDelegateHolders(viewManagers: List>): List = - trace("KotlinInteropModuleRegistry.extractViewManagersDelegateHolders") { - viewManagers.filterIsInstance() - } - /** * Since React Native v0.55, {@link com.facebook.react.ReactPackage#createViewManagers(ReactApplicationContext)} * gets called only once per lifetime of {@link com.facebook.react.ReactInstanceManager}. @@ -87,13 +73,12 @@ class KotlinInteropModuleRegistry( * the instance that was bound with the prop method won't be the same as the instance returned by module registry. * To fix that we need to update all modules holder in exported view managers. */ - fun updateModuleHoldersInViewManagers(viewWrapperHolders: List) = - trace("KotlinInteropModuleRegistry.updateModuleHoldersInViewManagers") { - viewWrapperHolders - .map { it.viewWrapperDelegate } - .forEach { holderWrapper -> - holderWrapper.moduleHolder = requireNotNull(registry.getModuleHolder(holderWrapper.moduleHolder.name)) { - "Cannot update the module holder for ${holderWrapper.moduleHolder.name}." + fun updateModuleHoldersInViewDelegates(viewDelegates: List) = + trace("KotlinInteropModuleRegistry.updateModuleHoldersInViewDelegates") { + viewDelegates + .forEach { delegate -> + delegate.moduleHolder = requireNotNull(registry.getModuleHolder(delegate.moduleHolder.name)) { + "Cannot update the module holder for ${delegate.moduleHolder.name}." } } } diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/jni/fabric/NativeStatePropsGetter.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/jni/fabric/NativeStatePropsGetter.kt new file mode 100644 index 00000000000000..7f966fafad8163 --- /dev/null +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/jni/fabric/NativeStatePropsGetter.kt @@ -0,0 +1,9 @@ +package expo.modules.kotlin.jni.fabric + +import com.facebook.yoga.annotations.DoNotStrip + +@DoNotStrip +class NativeStatePropsGetter { + // We can't use StateWrapper directly, as this class is not exposed + external fun getStateProps(stateWrapper: Any): Map? +} diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/AnyViewProp.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/AnyViewProp.kt index 8a62e37a9f789f..014c0badeaa0b3 100644 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/AnyViewProp.kt +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/AnyViewProp.kt @@ -11,5 +11,9 @@ abstract class AnyViewProp( ) { abstract fun set(prop: Dynamic, onView: View, appContext: AppContext? = null) + abstract fun set(prop: Any?, onView: View, appContext: AppContext? = null) + abstract val isNullable: Boolean + + internal abstract val isStateProp: Boolean } diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt index 721a198aaff9aa..aeb2a5819a5740 100644 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt @@ -12,16 +12,34 @@ open class ConcreteViewProp( propType: AnyType, protected val setter: (view: ViewType, prop: PropType) -> Unit ) : AnyViewProp(name, propType) { - @Suppress("UNCHECKED_CAST") + private var _isStateProp = false + override fun set(prop: Dynamic, onView: View, appContext: AppContext?) { + setPropDirectly(prop, onView, appContext) + } + + override fun set(prop: Any?, onView: View, appContext: AppContext?) { + setPropDirectly(prop, onView, appContext) + } + + private fun setPropDirectly(prop: Any?, onView: View, appContext: AppContext?) { exceptionDecorator({ PropSetException(name, onView::class, it) }) { + @Suppress("UNCHECKED_CAST") setter(onView as ViewType, type.convert(prop, appContext) as PropType) } } override val isNullable: Boolean = propType.kType.isMarkedNullable + + override val isStateProp: Boolean + get() = _isStateProp + + fun asStateProp(): ConcreteViewProp { + _isStateProp = true + return this + } } class ConcreteViewPropWithDefault( diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt index ae83223d6b1cb3..8bd73f4f4157d2 100644 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt @@ -12,7 +12,7 @@ import expo.modules.core.utilities.ifNull class GroupViewManagerWrapper( override val viewWrapperDelegate: ViewManagerWrapperDelegate ) : ViewGroupManager(), ViewWrapperDelegateHolder { - override fun getName(): String = "ViewManagerAdapter_${viewWrapperDelegate.name}" + override fun getName(): String = viewWrapperDelegate.viewManagerName override fun createViewInstance(reactContext: ThemedReactContext): ViewGroup = viewWrapperDelegate.createView(reactContext) as ViewGroup @@ -21,6 +21,7 @@ class GroupViewManagerWrapper( val propsMap = props.getBackingMap() // Updates expo related properties. val handledProps = viewWrapperDelegate.updateProperties(viewToUpdate, propsMap) + viewWrapperDelegate.updateStateProps(viewToUpdate) // Updates remaining props using RN implementation. // To not triggered undefined setters we filtrated already handled properties. super.updateProperties( @@ -39,9 +40,9 @@ class GroupViewManagerWrapper( props: ReactStylesDiffMap?, stateWrapper: StateWrapper? ): Any? { - val view = view as? ExpoView ?: return null - view.stateWrapper = stateWrapper - return super.updateState(view, props, stateWrapper) + (view as? ExpoView)?.stateWrapper = stateWrapper + viewWrapperDelegate.updateStateProps(view) + return null } override fun getNativeProps(): MutableMap { @@ -58,7 +59,7 @@ class GroupViewManagerWrapper( } override fun getExportedCustomDirectEventTypeConstants(): Map { - val expoEvent = viewWrapperDelegate.getExportedCustomDirectEventTypeConstants() ?: emptyMap() + val expoEvent = viewWrapperDelegate.getExportedCustomDirectEventTypeConstants() return super.getExportedCustomDirectEventTypeConstants()?.plus(expoEvent) ?: expoEvent } diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt index 93c6647293f860..ab39cf043f4cf7 100644 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt @@ -3,13 +3,14 @@ package expo.modules.kotlin.views import android.view.View import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.SimpleViewManager +import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.getBackingMap class SimpleViewManagerWrapper( override val viewWrapperDelegate: ViewManagerWrapperDelegate ) : SimpleViewManager(), ViewWrapperDelegateHolder { - override fun getName(): String = "ViewManagerAdapter_${viewWrapperDelegate.name}" + override fun getName(): String = viewWrapperDelegate.viewManagerName override fun createViewInstance(reactContext: ThemedReactContext): View = viewWrapperDelegate.createView(reactContext) @@ -18,6 +19,7 @@ class SimpleViewManagerWrapper( val propsMap = props.getBackingMap() // Updates expo related properties. val handledProps = viewWrapperDelegate.updateProperties(viewToUpdate, propsMap) + viewWrapperDelegate.updateStateProps(viewToUpdate) // Updates remaining props using RN implementation. // To not triggered undefined setters we filtrated already handled properties. super.updateProperties( @@ -26,6 +28,16 @@ class SimpleViewManagerWrapper( ) } + override fun updateState( + view: View, + props: ReactStylesDiffMap?, + stateWrapper: StateWrapper? + ): Any? { + (view as? ExpoView)?.stateWrapper = stateWrapper + viewWrapperDelegate.updateStateProps(view) + return null + } + override fun onAfterUpdateTransaction(view: View) { super.onAfterUpdateTransaction(view) viewWrapperDelegate.onViewDidUpdateProps(view) @@ -45,7 +57,7 @@ class SimpleViewManagerWrapper( } override fun getExportedCustomDirectEventTypeConstants(): Map { - val expoEvent = viewWrapperDelegate.getExportedCustomDirectEventTypeConstants() ?: emptyMap() + val expoEvent = viewWrapperDelegate.getExportedCustomDirectEventTypeConstants() return super.getExportedCustomDirectEventTypeConstants()?.plus(expoEvent) ?: expoEvent } } diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ViewDefinitionBuilder.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ViewDefinitionBuilder.kt index 46d0a22a26fd01..6a36a6bf45a36b 100644 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ViewDefinitionBuilder.kt @@ -123,12 +123,14 @@ class ViewDefinitionBuilder( inline fun Prop( name: String, noinline body: (view: T, prop: PropType) -> Unit - ) { - props[name] = ConcreteViewProp( + ): ConcreteViewProp { + return ConcreteViewProp( name, toAnyType(), body - ) + ).apply { + props[name] = this + } } /** @@ -138,12 +140,14 @@ class ViewDefinitionBuilder( inline fun Prop( name: String, noinline body: (view: ViewType, prop: PropType) -> Unit - ) { - props[name] = ConcreteViewProp( + ): ConcreteViewProp { + return ConcreteViewProp( name, toAnyType(), body - ) + ).apply { + props[name] = this + } } /** @@ -154,13 +158,15 @@ class ViewDefinitionBuilder( name: String, defaultValue: PropType, noinline body: (view: ViewType, prop: PropType) -> Unit - ) { - props[name] = ConcreteViewPropWithDefault( + ): ConcreteViewPropWithDefault { + return ConcreteViewPropWithDefault( name, toAnyType(), body, defaultValue - ) + ).apply { + props[name] = this + } } inline fun PropGroup( diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt index 39e66b13962f8e..6feb45dcb9db58 100644 --- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt @@ -3,23 +3,34 @@ package expo.modules.kotlin.views import android.content.Context import android.view.View import com.facebook.react.bridge.ReadableMap +import com.facebook.react.uimanager.ViewManager import expo.modules.kotlin.ModuleHolder import expo.modules.kotlin.events.normalizeEventName import expo.modules.kotlin.exception.OnViewDidUpdatePropsException import expo.modules.kotlin.exception.exceptionDecorator import expo.modules.kotlin.exception.toCodedException +import expo.modules.kotlin.jni.fabric.NativeStatePropsGetter import expo.modules.kotlin.logger -class ViewManagerWrapperDelegate(internal var moduleHolder: ModuleHolder<*>, internal val definition: ViewManagerDefinition, internal val delegateName: String? = null) { +class ViewManagerWrapperDelegate( + internal var moduleHolder: ModuleHolder<*>, + internal val definition: ViewManagerDefinition, + internal val delegateName: String? = null +) { internal val viewGroupDefinition: ViewGroupDefinition? get() = definition.viewGroupDefinition val name: String get() = delegateName ?: "${moduleHolder.name}_${definition.name}" + val viewManagerName: String + get() = "ViewManagerAdapter_$name" + val props: Map get() = definition.props + val hasStateProps: Boolean = definition.props.values.any { it.isStateProp } + fun createView(context: Context): View { return definition .createView(context, moduleHolder.module.appContext) @@ -47,6 +58,13 @@ class ViewManagerWrapperDelegate(internal var moduleHolder: ModuleHolder<*>, int } } + fun toRNViewManager(): ViewManager<*, *> { + return when (definition.getViewManagerType()) { + ViewManagerType.SIMPLE -> SimpleViewManagerWrapper(this) + ViewManagerType.GROUP -> GroupViewManagerWrapper(this) + } + } + /** * Updates the expo related properties of a given View based on a ReadableMap of property values. * @@ -62,28 +80,70 @@ class ViewManagerWrapperDelegate(internal var moduleHolder: ModuleHolder<*>, int while (iterator.hasNextKey()) { val key = iterator.nextKey() - expoProps[key]?.let { expoProp -> + expoProps[key] + ?.takeIf { !it.isStateProp } + ?.let { expoProp -> + try { + expoProp.set(propsMap.getDynamic(key), view, moduleHolder.module.appContext) + } catch (exception: Throwable) { + // The view wasn't constructed correctly, so errors are expected. + // We can ignore them. + if (view.isErrorView()) { + return@let + } + + val codedException = exception.toCodedException() + logger.error("❌ Cannot set the '$name' prop on the '$view'", codedException) + definition.handleException( + view, + codedException + ) + } finally { + handledProps.add(key) + } + } + } + return handledProps + } + + fun updateStateProps(view: View) { + if (!hasStateProps) { + return + } + + val expoView = view as? ExpoView + ?: return + + val stateWrapper = expoView.stateWrapper + ?: return + + val stateProps = NativeStatePropsGetter() + .getStateProps(stateWrapper) + ?: return + + stateProps.forEach { (propName, value) -> + val prop = props[propName] + if (prop != null && prop.isStateProp) { try { - expoProp.set(propsMap.getDynamic(key), view, moduleHolder.module.appContext) + prop.set(value, view, moduleHolder.module.appContext) } catch (exception: Throwable) { // The view wasn't constructed correctly, so errors are expected. // We can ignore them. if (view.isErrorView()) { - return@let + return@forEach } val codedException = exception.toCodedException() - logger.error("❌ Cannot set the '$name' prop on the '$view'", codedException) + logger.error("❌ Cannot set the '$propName' state prop on the '$view'", codedException) definition.handleException( view, codedException ) - } finally { - handledProps.add(key) } + } else { + logger.warn("⚠️ Tried to set unknown or non-state prop '$propName' on '$view'") } } - return handledProps } fun onDestroy(view: View) { diff --git a/packages/expo-modules-core/common/cpp/fabric/ExpoViewComponentDescriptor.cpp b/packages/expo-modules-core/common/cpp/fabric/ExpoViewComponentDescriptor.cpp deleted file mode 100644 index 1752b22b787060..00000000000000 --- a/packages/expo-modules-core/common/cpp/fabric/ExpoViewComponentDescriptor.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2022-present 650 Industries. All rights reserved. - -#include "ExpoViewComponentDescriptor.h" -#include -#include - -namespace expo { - -ExpoViewComponentDescriptor::ExpoViewComponentDescriptor(facebook::react::ComponentDescriptorParameters const ¶meters) - : facebook::react::ConcreteComponentDescriptor(parameters) { -} - -facebook::react::ComponentHandle ExpoViewComponentDescriptor::getComponentHandle() const { - return reinterpret_cast(getComponentName()); -} - -facebook::react::ComponentName ExpoViewComponentDescriptor::getComponentName() const { - return std::static_pointer_cast(this->flavor_)->c_str(); -} - -void ExpoViewComponentDescriptor::adopt(facebook::react::ShadowNode &shadowNode) const { - react_native_assert(dynamic_cast(&shadowNode)); - - const auto snode = dynamic_cast(&shadowNode); - const auto state = snode->getStateData(); - - auto width = state._width; - auto height = state._height; - - if (!isnan(width) || !isnan(height)) { - auto const &props = *std::static_pointer_cast(snode->getProps()); - - // The node has width and/or height set as style props, so we should not override it - auto widthProp = props.yogaStyle.dimension(facebook::yoga::Dimension::Width); - auto heightProp = props.yogaStyle.dimension(facebook::yoga::Dimension::Height); - - if (widthProp.value().isDefined()) { - // view has fixed dimension size set in props, so we should not autosize it in that axis - width = widthProp.value().unwrap(); - } - if (heightProp.value().isDefined()) { - height = heightProp.value().unwrap(); - } - - snode->setSize({width, height}); - } - - // handle layout style prop update - auto styleWidth = state._styleWidth; - auto styleHeight = state._styleHeight; - - if (!isnan(styleWidth) || !isnan(styleHeight)) { - auto const &props = *std::static_pointer_cast(snode->getProps()); - - auto& style = const_cast(props.yogaStyle); - bool changedStyle = false; - - if (!isnan(styleWidth)) { - style.setDimension(facebook::yoga::Dimension::Width, facebook::yoga::StyleSizeLength::points(styleWidth)); - changedStyle = true; - } - - if (!isnan(styleHeight)) { - style.setDimension(facebook::yoga::Dimension::Height, facebook::yoga::StyleSizeLength::points(styleHeight)); - changedStyle = true; - } - - // Update yoga props and dirty layout if we changed the style - if (changedStyle) { - auto* expoNode = const_cast(snode); - expoNode->updateYogaProps(); - expoNode->dirtyLayout(); - } - } - ConcreteComponentDescriptor::adopt(shadowNode); -} - -} // namespace expo diff --git a/packages/expo-modules-core/common/cpp/fabric/ExpoViewComponentDescriptor.h b/packages/expo-modules-core/common/cpp/fabric/ExpoViewComponentDescriptor.h index b287766a209ae6..5f2ab6594d3feb 100644 --- a/packages/expo-modules-core/common/cpp/fabric/ExpoViewComponentDescriptor.h +++ b/packages/expo-modules-core/common/cpp/fabric/ExpoViewComponentDescriptor.h @@ -11,16 +11,84 @@ namespace expo { -class ExpoViewComponentDescriptor : public facebook::react::ConcreteComponentDescriptor { - public: +template> +class ExpoViewComponentDescriptor + : public facebook::react::ConcreteComponentDescriptor { +public: using Flavor = std::shared_ptr; - ExpoViewComponentDescriptor(facebook::react::ComponentDescriptorParameters const ¶meters); + ExpoViewComponentDescriptor( + facebook::react::ComponentDescriptorParameters const ¶meters, + react::RawPropsParser &&rawPropsParser = {} + ) : facebook::react::ConcreteComponentDescriptor(parameters, std::move(rawPropsParser)) {} - facebook::react::ComponentHandle getComponentHandle() const override; - facebook::react::ComponentName getComponentName() const override; + facebook::react::ComponentHandle getComponentHandle() const override { + return reinterpret_cast(getComponentName()); + } - void adopt(facebook::react::ShadowNode &shadowNode) const override; + facebook::react::ComponentName getComponentName() const override { + return std::static_pointer_cast(this->flavor_)->c_str(); + } + + void adopt(facebook::react::ShadowNode &shadowNode) const override { + react_native_assert(dynamic_cast(&shadowNode)); + + const auto snode = dynamic_cast(&shadowNode); + const auto state = snode->getStateData(); + + auto width = state._width; + auto height = state._height; + + if (!isnan(width) || !isnan(height)) { + auto const &props = *std::static_pointer_cast( + snode->getProps()); + + // The node has width and/or height set as style props, so we should not override it + auto widthProp = props.yogaStyle.dimension(facebook::yoga::Dimension::Width); + auto heightProp = props.yogaStyle.dimension(facebook::yoga::Dimension::Height); + + if (widthProp.value().isDefined()) { + // view has fixed dimension size set in props, so we should not autosize it in that axis + width = widthProp.value().unwrap(); + } + if (heightProp.value().isDefined()) { + height = heightProp.value().unwrap(); + } + + snode->setSize({width, height}); + } + + // handle layout style prop update + auto styleWidth = state._styleWidth; + auto styleHeight = state._styleHeight; + + if (!isnan(styleWidth) || !isnan(styleHeight)) { + auto const &props = *std::static_pointer_cast( + snode->getProps()); + + auto &style = const_cast(props.yogaStyle); + bool changedStyle = false; + + if (!isnan(styleWidth)) { + style.setDimension(facebook::yoga::Dimension::Width, + facebook::yoga::StyleSizeLength::points(styleWidth)); + changedStyle = true; + } + + if (!isnan(styleHeight)) { + style.setDimension(facebook::yoga::Dimension::Height, + facebook::yoga::StyleSizeLength::points(styleHeight)); + changedStyle = true; + } + + // Update yoga props and dirty layout if we changed the style + if (changedStyle) { + snode->updateYogaProps(); + snode->dirtyLayout(); + } + } + facebook::react::ConcreteComponentDescriptor::adopt(shadowNode); + } }; } // namespace expo diff --git a/packages/expo-modules-core/common/cpp/fabric/ExpoViewProps.cpp b/packages/expo-modules-core/common/cpp/fabric/ExpoViewProps.cpp index 7b68c7641ea8c0..8de5e31d424af2 100644 --- a/packages/expo-modules-core/common/cpp/fabric/ExpoViewProps.cpp +++ b/packages/expo-modules-core/common/cpp/fabric/ExpoViewProps.cpp @@ -11,13 +11,16 @@ namespace expo { /** Borrows the props map from the source props and applies the update given in the raw props. */ -std::unordered_map propsMapFromProps(const ExpoViewProps &sourceProps, const react::RawProps &rawProps) { +std::unordered_map propsMapFromProps( + const ExpoViewProps &sourceProps, + const react::RawProps &rawProps +) { std::unordered_map propsMap = sourceProps.propsMap; // Iterate over values in the raw props object. // Note that it contains only updated props. - const auto& dynamicRawProps = static_cast(rawProps); - for (const auto& propsPair : dynamicRawProps.items()) { + const auto &dynamicRawProps = static_cast(rawProps); + for (const auto &propsPair: dynamicRawProps.items()) { const auto &propName = propsPair.first.getString(); propsMap[propName] = static_cast(propsPair.second); } @@ -25,10 +28,17 @@ std::unordered_map propsMapFromProps(const ExpoView return propsMap; } -ExpoViewProps::ExpoViewProps(const react::PropsParserContext &context, - const ExpoViewProps &sourceProps, - const react::RawProps &rawProps) - : react::ViewProps(context, sourceProps, rawProps), - propsMap(propsMapFromProps(sourceProps, rawProps)) {} +ExpoViewProps::ExpoViewProps( + const react::PropsParserContext &context, + const ExpoViewProps &sourceProps, + const react::RawProps &rawProps, + [[maybe_unused]] const std::function &filterObjectKeys +) +#if EXPO_VIEW_PROPS_SUPPORTS_FILTER_OBJECT_KEYS + : react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), +#else + : react::ViewProps(context, sourceProps, rawProps), +#endif + propsMap(propsMapFromProps(sourceProps, rawProps)) {} } // namespace expo diff --git a/packages/expo-modules-core/common/cpp/fabric/ExpoViewProps.h b/packages/expo-modules-core/common/cpp/fabric/ExpoViewProps.h index 0f30463583ce66..fb0165bce2f89c 100644 --- a/packages/expo-modules-core/common/cpp/fabric/ExpoViewProps.h +++ b/packages/expo-modules-core/common/cpp/fabric/ExpoViewProps.h @@ -8,14 +8,29 @@ #include #include +#ifdef __APPLE__ +#include +#endif + +// macOS ViewProps doesn't support filterObjectKeys parameter +#if defined(TARGET_OS_OSX) && TARGET_OS_OSX +#define EXPO_VIEW_PROPS_SUPPORTS_FILTER_OBJECT_KEYS 0 +#else +#define EXPO_VIEW_PROPS_SUPPORTS_FILTER_OBJECT_KEYS 1 +#endif + namespace expo { class ExpoViewProps : public facebook::react::ViewProps { public: ExpoViewProps() = default; - ExpoViewProps(const facebook::react::PropsParserContext &context, - const ExpoViewProps &sourceProps, - const facebook::react::RawProps &rawProps); + + ExpoViewProps( + const facebook::react::PropsParserContext &context, + const ExpoViewProps &sourceProps, + const facebook::react::RawProps &rawProps, + const std::function &filterObjectKeys = nullptr + ); #pragma mark - Props diff --git a/packages/expo-modules-core/common/cpp/fabric/ExpoViewShadowNode.cpp b/packages/expo-modules-core/common/cpp/fabric/ExpoViewShadowNode.cpp index a7656cb00e2b3f..8c9f20181f2832 100644 --- a/packages/expo-modules-core/common/cpp/fabric/ExpoViewShadowNode.cpp +++ b/packages/expo-modules-core/common/cpp/fabric/ExpoViewShadowNode.cpp @@ -8,38 +8,4 @@ namespace expo { extern const char ExpoViewComponentName[] = "ExpoFabricView"; -ExpoViewShadowNode::ExpoViewShadowNode( - const react::ShadowNodeFragment &fragment, - const react::ShadowNodeFamily::Shared &family, - react::ShadowNodeTraits traits) - : ConcreteViewShadowNode(fragment, family, traits) { - initialize(); -} - -ExpoViewShadowNode::ExpoViewShadowNode( - const react::ShadowNode &sourceShadowNode, - const react::ShadowNodeFragment &fragment) - : ConcreteViewShadowNode(sourceShadowNode, fragment) { - initialize(); -} - -void ExpoViewShadowNode::initialize() noexcept { - auto &viewProps = static_cast(*props_); - - if (viewProps.collapsableChildren) { - traits_.set(react::ShadowNodeTraits::Trait::ChildrenFormStackingContext); - } else { - traits_.unset(react::ShadowNodeTraits::Trait::ChildrenFormStackingContext); - } - - if (YGNodeStyleGetDisplay(&yogaNode_) == YGDisplayContents) { - auto it = viewProps.propsMap.find("disableForceFlatten"); - bool disableForceFlatten = (it != viewProps.propsMap.end()) ? it->second.getBool() : false; - - if (disableForceFlatten) { - traits_.unset(react::ShadowNodeTraits::Trait::ForceFlattenView); - } - } -} - } // namespace expo diff --git a/packages/expo-modules-core/common/cpp/fabric/ExpoViewShadowNode.h b/packages/expo-modules-core/common/cpp/fabric/ExpoViewShadowNode.h index f889199fd91098..d7d5d8f9799574 100644 --- a/packages/expo-modules-core/common/cpp/fabric/ExpoViewShadowNode.h +++ b/packages/expo-modules-core/common/cpp/fabric/ExpoViewShadowNode.h @@ -14,27 +14,62 @@ namespace expo { extern const char ExpoViewComponentName[]; -class ExpoViewShadowNode final : public facebook::react::ConcreteViewShadowNode< - ExpoViewComponentName, ExpoViewProps, - ExpoViewEventEmitter, ExpoViewState> { +template +class ExpoViewShadowNode : public facebook::react::ConcreteViewShadowNode< + ExpoViewComponentName, + ViewProps, + ExpoViewEventEmitter, + ViewState +> { public: + typedef facebook::react::ConcreteViewShadowNode< + ExpoViewComponentName, + ViewProps, + ExpoViewEventEmitter, + ViewState + > ConcreteViewShadowNode; + using ConcreteViewShadowNode::ConcreteViewShadowNode; - ExpoViewShadowNode(const facebook::react::ShadowNodeFragment &fragment, - const facebook::react::ShadowNodeFamily::Shared &family, - facebook::react::ShadowNodeTraits traits); + ExpoViewShadowNode( + const facebook::react::ShadowNodeFragment &fragment, + const facebook::react::ShadowNodeFamily::Shared &family, + facebook::react::ShadowNodeTraits traits + ) : ConcreteViewShadowNode(fragment, family, traits) { + initialize(); + } - ExpoViewShadowNode(const facebook::react::ShadowNode &sourceShadowNode, - const facebook::react::ShadowNodeFragment &fragment); + ExpoViewShadowNode( + const facebook::react::ShadowNode &sourceShadowNode, + const facebook::react::ShadowNodeFragment &fragment + ) : ConcreteViewShadowNode(sourceShadowNode, fragment) { + initialize(); + } -public: static facebook::react::ShadowNodeTraits BaseTraits() { auto traits = ConcreteViewShadowNode::BaseTraits(); return traits; } private: - void initialize() noexcept; + void initialize() noexcept { + auto &viewProps = static_cast(*this->props_); + + if (viewProps.collapsableChildren) { + this->traits_.set(react::ShadowNodeTraits::Trait::ChildrenFormStackingContext); + } else { + this->traits_.unset(react::ShadowNodeTraits::Trait::ChildrenFormStackingContext); + } + + if (YGNodeStyleGetDisplay(&this->yogaNode_) == YGDisplayContents) { + auto it = viewProps.propsMap.find("disableForceFlatten"); + bool disableForceFlatten = (it != viewProps.propsMap.end()) && it->second.getBool(); + + if (disableForceFlatten) { + this->traits_.unset(react::ShadowNodeTraits::Trait::ForceFlattenView); + } + } + } }; } // namespace expo diff --git a/packages/expo-modules-core/common/cpp/fabric/ExpoViewState.cpp b/packages/expo-modules-core/common/cpp/fabric/ExpoViewState.cpp new file mode 100644 index 00000000000000..c4087332309135 --- /dev/null +++ b/packages/expo-modules-core/common/cpp/fabric/ExpoViewState.cpp @@ -0,0 +1,37 @@ +#include "ExpoViewState.h" + +#ifdef __cplusplus + +namespace expo { + +ExpoViewState::ExpoViewState(float width, float height) { + if (width >= 0) { + _width = width; + } else { + _width = std::numeric_limits::quiet_NaN(); + } + if (height >= 0) { + _height = height; + } else { + _height = std::numeric_limits::quiet_NaN(); + } +} + +ExpoViewState ExpoViewState::withStyleDimensions(float styleWidth, float styleHeight) { + ExpoViewState state; + if (styleWidth >= 0) { + state._styleWidth = styleWidth; + } else { + state._styleWidth = std::numeric_limits::quiet_NaN(); + } + if (styleHeight >= 0) { + state._styleHeight = styleHeight; + } else { + state._styleHeight = std::numeric_limits::quiet_NaN(); + } + return state; +} + +} // namespace expo + +#endif // __cplusplus diff --git a/packages/expo-modules-core/common/cpp/fabric/ExpoViewState.h b/packages/expo-modules-core/common/cpp/fabric/ExpoViewState.h index 89dee43eaa4013..4559f7a4b07522 100644 --- a/packages/expo-modules-core/common/cpp/fabric/ExpoViewState.h +++ b/packages/expo-modules-core/common/cpp/fabric/ExpoViewState.h @@ -4,72 +4,24 @@ #ifdef __cplusplus -#ifdef ANDROID -#include -#include -#include -#endif +#include namespace expo { -class ExpoViewState final { +class ExpoViewState { public: - ExpoViewState() {} + ExpoViewState() = default; + + virtual ~ExpoViewState() = default; - ExpoViewState(float width, float height) { - if (width >= 0) { - _width = width; - } else { - _width = std::numeric_limits::quiet_NaN(); - } - if (height >= 0) { - _height = height; - } else { - _height = std::numeric_limits::quiet_NaN(); - } - }; + ExpoViewState(float width, float height); - static ExpoViewState withStyleDimensions(float styleWidth, float styleHeight) { - ExpoViewState state; - if (styleWidth >= 0) { - state._styleWidth = styleWidth; - } else { - state._styleWidth = std::numeric_limits::quiet_NaN(); - } - if (styleHeight >= 0) { - state._styleHeight = styleHeight; - } else { - state._styleHeight = std::numeric_limits::quiet_NaN(); - } - return state; - } - -#ifdef ANDROID - ExpoViewState(ExpoViewState const &previousState, folly::dynamic data) - : _width(isNonnullProperty(data, "width") ? (float)data["width"].getDouble() : std::numeric_limits::quiet_NaN()), - _height(isNonnullProperty(data, "height") ? (float)data["height"].getDouble() : std::numeric_limits::quiet_NaN()), - _styleWidth(isNonnullProperty(data, "styleWidth") ? (float)data["styleWidth"].getDouble() : std::numeric_limits::quiet_NaN()), - _styleHeight(isNonnullProperty(data, "styleHeight") ? (float)data["styleHeight"].getDouble() : std::numeric_limits::quiet_NaN()) { - } - - folly::dynamic getDynamic() const { - return {}; - }; + static ExpoViewState withStyleDimensions(float styleWidth, float styleHeight); - facebook::react::MapBuffer getMapBuffer() const { - return facebook::react::MapBufferBuilder::EMPTY(); - }; - - static inline bool isNonnullProperty(const folly::dynamic &value, const std::string &name) { - return value.count(name) && !value[name].isNull(); - } -#endif - float _width = std::numeric_limits::quiet_NaN(); float _height = std::numeric_limits::quiet_NaN(); float _styleWidth = std::numeric_limits::quiet_NaN(); float _styleHeight = std::numeric_limits::quiet_NaN(); - }; } // namespace expo diff --git a/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIVirtualViewObjC.mm b/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIVirtualViewObjC.mm index 512bef9e91fcb9..774a27ba145e60 100644 --- a/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIVirtualViewObjC.mm +++ b/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIVirtualViewObjC.mm @@ -80,12 +80,12 @@ id convertFollyDynamicToId(const folly::dynamic &dyn) Cache for component flavors, where the key is a view class name and value is the flavor. Flavors must be cached in order to keep using the same component handle after app reloads. */ -static std::unordered_map _componentFlavorsCache; +static std::unordered_map::Flavor> _componentFlavorsCache; @implementation SwiftUIVirtualViewObjC { react::SharedViewProps _props; react::SharedViewEventEmitter _eventEmitter; - expo::ExpoViewShadowNode::ConcreteState::Shared _state; + expo::ExpoViewShadowNode<>::ConcreteState::Shared _state; } - (instancetype)init @@ -213,7 +213,7 @@ - (void)layoutSubviews // We're caching the flavor pointer so that the component handle stay the same for the same class name. // Otherwise, the component handle would change after reload which may cause memory leaks and unexpected view recycling behavior. - expo::ExpoViewComponentDescriptor::Flavor flavor = _componentFlavorsCache[className]; + expo::ExpoViewComponentDescriptor<>::Flavor flavor = _componentFlavorsCache[className]; if (flavor == nullptr) { flavor = _componentFlavorsCache[className] = std::make_shared(className); @@ -226,7 +226,7 @@ - (void)layoutSubviews componentHandle, componentName, flavor, - &facebook::react::concreteComponentDescriptorConstructor + &facebook::react::concreteComponentDescriptorConstructor> }; } @@ -343,7 +343,7 @@ - (void)updateProps:(nonnull NSDictionary *)props - (void)updateState:(react::State::Shared const &)state oldState:(react::State::Shared const &)oldState { - _state = std::static_pointer_cast(state); + _state = std::static_pointer_cast::ConcreteState>(state); } - (void)viewDidUpdateProps diff --git a/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm b/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm index a9d1b453bbf499..e7f1dd16bf6fde 100644 --- a/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm +++ b/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm @@ -76,10 +76,10 @@ id convertFollyDynamicToId(const folly::dynamic &dyn) Cache for component flavors, where the key is a view class name and value is the flavor. Flavors must be cached in order to keep using the same component handle after app reloads. */ -static std::unordered_map _componentFlavorsCache; +static std::unordered_map::Flavor> _componentFlavorsCache; @implementation ExpoFabricViewObjC { - ExpoViewShadowNode::ConcreteState::Shared _state; + ExpoViewShadowNode<>::ConcreteState::Shared _state; } - (instancetype)initWithFrame:(CGRect)frame @@ -99,7 +99,7 @@ - (instancetype)initWithFrame:(CGRect)frame // We're caching the flavor pointer so that the component handle stay the same for the same class name. // Otherwise, the component handle would change after reload which may cause memory leaks and unexpected view recycling behavior. - ExpoViewComponentDescriptor::Flavor flavor = _componentFlavorsCache[className]; + ExpoViewComponentDescriptor<>::Flavor flavor = _componentFlavorsCache[className]; if (flavor == nullptr) { flavor = _componentFlavorsCache[className] = std::make_shared(className); @@ -112,7 +112,7 @@ - (instancetype)initWithFrame:(CGRect)frame componentHandle, componentName, flavor, - &facebook::react::concreteComponentDescriptorConstructor + &facebook::react::concreteComponentDescriptorConstructor> }; } @@ -161,7 +161,7 @@ - (void)updateProps:(nonnull NSDictionary *)props - (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState { - _state = std::static_pointer_cast(state); + _state = std::static_pointer_cast::ConcreteState>(state); } - (void)viewDidUpdateProps