From 0e53ad32d008173e8dbbdc1f94ac572a64260be6 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 10 Apr 2024 21:09:28 +0200 Subject: [PATCH 01/12] Temp encoding test --- .../dev/gitlive/firebase/EncodersTest.kt | 33 +++++++++++++++++++ .../kotlin/dev/gitlive/firebase/TestUtils.kt | 2 +- .../kotlin/dev/gitlive/firebase/TestUtils.kt | 2 +- .../kotlin/dev/gitlive/firebase/TestUtils.kt | 2 +- .../kotlin/dev/gitlive/firebase/TestUtils.kt | 2 +- 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 5fdeaf72c..461cc62cd 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -8,12 +8,14 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.nullable import kotlinx.serialization.builtins.serializer import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlin.jvm.JvmInline import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNull @Serializable object TestObject { @@ -71,6 +73,15 @@ data class NestedClass( class EncodersTest { + @Test + fun encodeDecodeNullableString() { + val encoded = encode(null) { encodeDefaults = true } + + nativeAssertEquals(null, encoded) + + val decoded = decode(String.serializer().nullable, encoded) + assertNull(decoded) + } @Test fun encodeDecodeList() { val list = listOf("One", "Two", "Three") @@ -82,6 +93,17 @@ class EncodersTest { assertEquals(listOf("One", "Two", "Three"), decoded) } + @Test + fun encodeDecodeNullableList() { + val list = listOf("One", "Two", null) + val encoded = encode>(list) { encodeDefaults = true } + + nativeAssertEquals(nativeListOf("One", "Two", null), encoded) + + val decoded = decode(ListSerializer(String.serializer().nullable), encoded) + assertEquals(listOf("One", "Two", null), decoded) + } + @Test fun encodeDecodeMap() { val map = mapOf("key" to "value", "key2" to "value2", "key3" to "value3") @@ -93,6 +115,17 @@ class EncodersTest { assertEquals(mapOf("key" to "value", "key2" to "value2", "key3" to "value3"), decoded) } + @Test + fun encodeDecodeNullableMap() { + val map = mapOf("key" to "value", "key2" to "value2", "key3" to null) + val encoded = encode>(map) { encodeDefaults = true } + + nativeAssertEquals(nativeMapOf("key" to "value", "key2" to "value2", "key3" to null), encoded) + + val decoded = decode(MapSerializer(String.serializer(), String.serializer().nullable), encoded) + assertEquals(mapOf("key" to "value", "key2" to "value2", "key3" to null), decoded) + } + @Test fun encodeDecodeObject() { val encoded = encode(TestObject.serializer(), TestObject) { encodeDefaults = false } diff --git a/test-utils/src/androidMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/androidMain/kotlin/dev/gitlive/firebase/TestUtils.kt index 1a4635f85..df47b7bba 100644 --- a/test-utils/src/androidMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/androidMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -13,7 +13,7 @@ actual fun runTest(test: suspend CoroutineScope.() -> Unit) = kotlinx.coroutines actual fun runBlockingTest(action: suspend CoroutineScope.() -> Unit) = runBlocking(block = action) actual fun nativeMapOf(vararg pairs: Pair): Any = mapOf(*pairs) -actual fun nativeListOf(vararg elements: Any): Any = listOf(*elements) +actual fun nativeListOf(vararg elements: Any?): Any = listOf(*elements) actual fun nativeAssertEquals(expected: Any?, actual: Any?) { kotlin.test.assertEquals(expected, actual) } diff --git a/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt index bf9fcdc19..fc2f24479 100644 --- a/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -13,5 +13,5 @@ expect fun runTest(test: suspend CoroutineScope.() -> Unit): TestResult expect fun runBlockingTest(action: suspend CoroutineScope.() -> Unit) expect fun nativeMapOf(vararg pairs: Pair): Any -expect fun nativeListOf(vararg elements: Any): Any +expect fun nativeListOf(vararg elements: Any?): Any expect fun nativeAssertEquals(expected: Any?, actual: Any?) diff --git a/test-utils/src/iosMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/iosMain/kotlin/dev/gitlive/firebase/TestUtils.kt index 6ba4248a4..ca7ee4a54 100644 --- a/test-utils/src/iosMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/iosMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -28,7 +28,7 @@ actual fun runTest(test: suspend CoroutineScope.() -> Unit) = runBlocking { } actual fun runBlockingTest(action: suspend CoroutineScope.() -> Unit) = runBlocking(block = action) actual fun nativeMapOf(vararg pairs: Pair): Any = mapOf(*pairs) -actual fun nativeListOf(vararg elements: Any): Any = listOf(*elements) +actual fun nativeListOf(vararg elements: Any?): Any = listOf(*elements) actual fun nativeAssertEquals(expected: Any?, actual: Any?) { kotlin.test.assertEquals(expected, actual) } diff --git a/test-utils/src/jsMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/jsMain/kotlin/dev/gitlive/firebase/TestUtils.kt index eac94efcf..5f87d1686 100644 --- a/test-utils/src/jsMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/jsMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -15,7 +15,7 @@ actual fun runBlockingTest(action: suspend CoroutineScope.() -> Unit) { } actual fun nativeMapOf(vararg pairs: Pair): Any = json(*pairs.map { (key, value) -> ((key as? String) ?: JSON.stringify(key)) to value }.toTypedArray()) -actual fun nativeListOf(vararg elements: Any): Any = elements +actual fun nativeListOf(vararg elements: Any?): Any = elements actual fun nativeAssertEquals(expected: Any?, actual: Any?) { kotlin.test.assertEquals(JSON.stringify(expected), JSON.stringify(actual)) } From 06dd193c679660e5d8e70032954d69e89a71e45f Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Thu, 11 Apr 2024 10:25:42 +0200 Subject: [PATCH 02/12] Adding fixes for OnDisconnect --- .../dev/gitlive/firebase/database/database.kt | 9 +- .../dev/gitlive/firebase/database/database.kt | 8 +- .../dev/gitlive/firebase/database/database.kt | 231 ++++++++++-------- .../dev/gitlive/firebase/database/database.kt | 6 +- .../dev/gitlive/firebase/database/database.kt | 10 +- test/database.rules.json | 12 +- 6 files changed, 163 insertions(+), 113 deletions(-) diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index ffdcc32a9..772bbd900 100644 --- a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -87,6 +87,10 @@ actual class FirebaseDatabase private constructor(val android: com.google.fireba actual fun useEmulator(host: String, port: Int) = android.useEmulator(host, port) + + actual fun goOffline() = android.goOffline() + + actual fun goOnline() = android.goOnline() } internal actual open class NativeQuery( @@ -293,8 +297,9 @@ internal actual class NativeOnDisconnect internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun updateEncodedChildren(encodedUpdate: Map) = - android.updateChildren(encodedUpdate) + @Suppress("UNCHECKED_CAST") + actual suspend fun updateEncodedChildren(encodedUpdate: Any?) = + android.updateChildren(encodedUpdate as Map) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } } diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index d1e272a38..1236d8608 100644 --- a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -37,6 +37,10 @@ expect class FirebaseDatabase { fun setPersistenceEnabled(enabled: Boolean) fun setLoggingEnabled(enabled: Boolean) fun useEmulator(host: String, port: Int) + + fun goOffline() + + fun goOnline() } data class ChildEvent internal constructor( @@ -140,7 +144,7 @@ internal expect class NativeOnDisconnect { suspend fun removeValue() suspend fun cancel() suspend fun setValue(encodedValue: Any?) - suspend fun updateEncodedChildren(encodedUpdate: Map) + suspend fun updateEncodedChildren(encodedUpdate: Any?) } class OnDisconnect internal constructor(@PublishedApi internal val native: NativeOnDisconnect) { @@ -156,7 +160,7 @@ class OnDisconnect internal constructor(@PublishedApi internal val native: Nativ setValue(strategy, value) { this.encodeDefaults = encodeDefaults } suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValue(encode(strategy, value, buildSettings)) - suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncodedChildren(update.mapValues { (_, it) -> encode(it, buildSettings) }) + suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncodedChildren(encode(update, buildSettings)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { this.encodeDefaults = encodeDefaults }")) suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update) { this.encodeDefaults = encodeDefaults diff --git a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt index ba3f7be59..60c209fa3 100644 --- a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -8,6 +8,7 @@ import dev.gitlive.firebase.runBlockingTest import dev.gitlive.firebase.runTest import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import kotlinx.serialization.Serializable @@ -16,6 +17,7 @@ import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.time.Duration.Companion.minutes @@ -41,9 +43,9 @@ class FirebaseDatabaseTest { FirebaseOptions( applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", - databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", + databaseUrl = "https://fir-kotlin-sdk-default-rtdb.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", - projectId = "fir-kotlin-sdk", + projectId = "fir-kotlin-sdk-default-rtdb", gcmSenderId = "846484016111" ) ) @@ -60,112 +62,127 @@ class FirebaseDatabaseTest { } } - @Test - fun testSetValue() = runTest { - ensureDatabaseConnected() - val testValue = "test" - val testReference = database.reference("testPath") - - testReference.setValue(testValue) - - val testReferenceValue = testReference - .valueEvents - .first() - .value() - - assertEquals(testValue, testReferenceValue) - } +// @Test +// fun testSetValue() = runTest { +// ensureDatabaseConnected() +// val testValue = "test" +// val testReference = database.reference("testPath") +// +// testReference.setValue(testValue) +// +// val testReferenceValue = testReference +// .valueEvents +// .first() +// .value() +// +// assertEquals(testValue, testReferenceValue) +// } +// +// @Test +// fun testChildCount() = runTest { +// setupRealtimeData() +// val dataSnapshot = database +// .reference("FirebaseRealtimeDatabaseTest") +// .valueEvents +// .first() +// +// val firebaseDatabaseChildCount = dataSnapshot.children.count() +// assertEquals(3, firebaseDatabaseChildCount) +// } +// +// @Test +// fun testBasicIncrementTransaction() = runTest { +// ensureDatabaseConnected() +// val data = DatabaseTest("PostOne", 2) +// val userRef = database.reference("users/user_1/post_id_1") +// setupDatabase(userRef, data, DatabaseTest.serializer()) +// +// // Check database before transaction +// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) +// assertEquals(data.title, userDocBefore.title) +// assertEquals(data.likes, userDocBefore.likes) +// +// // Run transaction +// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes + 1) } +// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) +// +// // Check the database after transaction +// assertEquals(data.title, userDocAfter.title) +// assertEquals(data.likes + 1, userDocAfter.likes) +// } +// +// @Test +// fun testBasicDecrementTransaction() = runTest { +// ensureDatabaseConnected() +// val data = DatabaseTest("PostTwo", 2) +// val userRef = database.reference("users/user_1/post_id_2") +// setupDatabase(userRef, data, DatabaseTest.serializer()) +// +// // Check database before transaction +// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) +// assertEquals(data.title, userDocBefore.title) +// assertEquals(data.likes, userDocBefore.likes) +// +// // Run transaction +// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes - 1) } +// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) +// +// // Check the database after transaction +// assertEquals(data.title, userDocAfter.title) +// assertEquals(data.likes - 1, userDocAfter.likes) +// } +// +// @Test +// fun testSetServerTimestamp() = runTest { +// ensureDatabaseConnected() +// val testReference = database.reference("testSetServerTimestamp") +// +// testReference.setValue(ServerValue.TIMESTAMP) +// +// val timestamp = testReference +// .valueEvents +// .first() +// .value() +// +// assertTrue(timestamp > 0) +// } +// +// @Test +// fun testIncrement() = runTest { +// ensureDatabaseConnected() +// val testReference = database.reference("testIncrement") +// +// testReference.setValue(2.0) +// +// val value = testReference +// .valueEvents +// .first() +// .value() +// +// assertEquals(2.0, value) +// +// testReference.setValue(ServerValue.increment(5.0)) +// val updatedValue = testReference +// .valueEvents +// .first() +// .value() +// +// assertEquals(7.0, updatedValue) +// } @Test - fun testChildCount() = runTest { + fun testUpdateChildren() = runTest { setupRealtimeData() - val dataSnapshot = database + val reference = database .reference("FirebaseRealtimeDatabaseTest") - .valueEvents - .first() - - val firebaseDatabaseChildCount = dataSnapshot.children.count() - assertEquals(3, firebaseDatabaseChildCount) - } + val valueEvents = reference.child("lastActivity").valueEvents + assertTrue(valueEvents.first().exists) + reference.onDisconnect().updateChildren(mapOf("test" to false, "nested" to mapOf("lastActivity" to null), "lastActivity" to null)) + database.goOffline() - @Test - fun testBasicIncrementTransaction() = runTest { + database.goOnline() ensureDatabaseConnected() - val data = DatabaseTest("PostOne", 2) - val userRef = database.reference("users/user_1/post_id_1") - setupDatabase(userRef, data, DatabaseTest.serializer()) - - // Check database before transaction - val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) - assertEquals(data.title, userDocBefore.title) - assertEquals(data.likes, userDocBefore.likes) - - // Run transaction - val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes + 1) } - val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) - - // Check the database after transaction - assertEquals(data.title, userDocAfter.title) - assertEquals(data.likes + 1, userDocAfter.likes) - } - - @Test - fun testBasicDecrementTransaction() = runTest { - ensureDatabaseConnected() - val data = DatabaseTest("PostTwo", 2) - val userRef = database.reference("users/user_1/post_id_2") - setupDatabase(userRef, data, DatabaseTest.serializer()) - - // Check database before transaction - val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) - assertEquals(data.title, userDocBefore.title) - assertEquals(data.likes, userDocBefore.likes) - - // Run transaction - val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes - 1) } - val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) - - // Check the database after transaction - assertEquals(data.title, userDocAfter.title) - assertEquals(data.likes - 1, userDocAfter.likes) - } - - @Test - fun testSetServerTimestamp() = runTest { - ensureDatabaseConnected() - val testReference = database.reference("testSetServerTimestamp") - - testReference.setValue(ServerValue.TIMESTAMP) - - val timestamp = testReference - .valueEvents - .first() - .value() - - assertTrue(timestamp > 0) - } - - @Test - fun testIncrement() = runTest { - ensureDatabaseConnected() - val testReference = database.reference("testIncrement") - - testReference.setValue(2.0) - - val value = testReference - .valueEvents - .first() - .value() - - assertEquals(2.0, value) - - testReference.setValue(ServerValue.increment(5.0)) - val updatedValue = testReference - .valueEvents - .first() - .value() - - assertEquals(7.0, updatedValue) + assertFalse(valueEvents.first().exists) } private suspend fun setupRealtimeData() { @@ -177,9 +194,13 @@ class FirebaseDatabaseTest { val firebaseDatabaseChildTest2 = FirebaseDatabaseChildTest("bbb") val firebaseDatabaseChildTest3 = FirebaseDatabaseChildTest("ccc") - firebaseDatabaseTestReference.child("1").setValue(firebaseDatabaseChildTest1) - firebaseDatabaseTestReference.child("2").setValue(firebaseDatabaseChildTest2) - firebaseDatabaseTestReference.child("3").setValue(firebaseDatabaseChildTest3) + val values = firebaseDatabaseTestReference.child("values") + values.child("1").setValue(firebaseDatabaseChildTest1) + values.child("2").setValue(firebaseDatabaseChildTest2) + values.child("3").setValue(firebaseDatabaseChildTest3) + firebaseDatabaseTestReference.child("lastActivity").setValue(1) + firebaseDatabaseTestReference.child("test").setValue(true) + firebaseDatabaseTestReference.child("nested").setValue(mapOf("lastActivity" to 0)) } private suspend fun setupDatabase(ref: DatabaseReference, data: T, strategy: SerializationStrategy) { diff --git a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt index a78a26fe1..e2042acab 100644 --- a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -69,6 +69,10 @@ actual class FirebaseDatabase internal constructor(val ios: FIRDatabase) { actual fun useEmulator(host: String, port: Int) = ios.useEmulatorWithHost(host, port.toLong()) + + actual fun goOffline() = ios.goOffline() + + actual fun goOnline() = ios.goOnline() } fun Type.toEventType() = when(this) { @@ -238,7 +242,7 @@ internal actual class NativeOnDisconnect internal constructor( } @Suppress("UNCHECKED_CAST") - actual suspend fun updateEncodedChildren(encodedUpdate: Map) { + actual suspend fun updateEncodedChildren(encodedUpdate: Any?) { ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate as Map, it) } } } diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index cf0023d0e..dea7e9288 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -46,6 +46,8 @@ import dev.gitlive.firebase.database.externals.OnDisconnect as JsOnDisconnect import dev.gitlive.firebase.database.externals.Query as JsQuery import dev.gitlive.firebase.database.externals.endAt as jsEndAt import dev.gitlive.firebase.database.externals.equalTo as jsEqualTo +import dev.gitlive.firebase.database.externals.goOffline as jsGoOffline +import dev.gitlive.firebase.database.externals.goOnline as jsGoOnline import dev.gitlive.firebase.database.externals.limitToFirst as jsLimitToFirst import dev.gitlive.firebase.database.externals.limitToLast as jsLimitToLast import dev.gitlive.firebase.database.externals.orderByChild as jsOrderByChild @@ -72,6 +74,10 @@ actual class FirebaseDatabase internal constructor(val js: Database) { actual fun setPersistenceEnabled(enabled: Boolean) {} actual fun setLoggingEnabled(enabled: Boolean) = rethrow { enableLogging(enabled) } actual fun useEmulator(host: String, port: Int) = rethrow { connectDatabaseEmulator(js, host, port) } + + actual fun goOffline() = rethrow { jsGoOffline(js) } + + actual fun goOnline() = rethrow { jsGoOnline(js) } } internal actual open class NativeQuery( @@ -228,8 +234,8 @@ internal actual class NativeOnDisconnect internal constructor( actual suspend fun setValue(encodedValue: Any?) = rethrow { js.set(encodedValue).awaitWhileOnline(database) } - actual suspend fun updateEncodedChildren(encodedUpdate: Map) = - rethrow { js.update(encodedUpdate).awaitWhileOnline(database) } + actual suspend fun updateEncodedChildren(encodedUpdate: Any?) = + rethrow { js.update(encodedUpdate ?: json()).awaitWhileOnline(database) } } diff --git a/test/database.rules.json b/test/database.rules.json index b104e9c24..9e0cf90a1 100644 --- a/test/database.rules.json +++ b/test/database.rules.json @@ -1,6 +1,16 @@ { "rules": { ".read": true, - ".write": true + ".write": true, + "FirebaseRealtimeDatabaseTest": { + "lastActivity": { + ".validate": "!newData.exists() || newData.isNumber()" + }, + "nested": { + "lastActivity": { + ".validate": "!newData.exists() || newData.isNumber()" + } + } + } } } \ No newline at end of file From 194e8028fb7010d4a7b6f55a0e28e265c8e1833c Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Thu, 11 Apr 2024 23:32:51 +0200 Subject: [PATCH 03/12] EncodedObject to make encoding more robust --- .../kotlin/dev/gitlive/firebase/_encoders.kt | 18 ++ .../kotlin/dev/gitlive/firebase/encoders.kt | 24 ++ .../dev/gitlive/firebase/EncodersTest.kt | 26 +- .../kotlin/dev/gitlive/firebase/_encoders.kt | 12 + .../kotlin/dev/gitlive/firebase/_encoders.kt | 14 + .../dev/gitlive/firebase/database/database.kt | 12 +- .../dev/gitlive/firebase/database/database.kt | 12 +- .../dev/gitlive/firebase/database/database.kt | 12 +- .../dev/gitlive/firebase/database/database.kt | 9 +- .../gitlive/firebase/firestore/firestore.kt | 69 +++-- .../gitlive/firebase/firestore/firestore.kt | 262 ++++++++++-------- .../gitlive/firebase/firestore/serializers.kt | 28 -- .../gitlive/firebase/firestore/firestore.kt | 138 ++++----- .../gitlive/firebase/firestore/firestore.kt | 70 ++--- 14 files changed, 404 insertions(+), 302 deletions(-) delete mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/serializers.kt diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt index 59ac0c431..bcfec9152 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -7,8 +7,26 @@ package dev.gitlive.firebase import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind +import java.lang.IllegalArgumentException import kotlin.collections.set +actual data class EncodedObject( + val android: Map +) + +actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) + +@PublishedApi +internal actual fun Map<*, *>.asEncodedObject() = EncodedObject( + map { (key, value) -> + if (key is String) { + key to value + } else { + throw IllegalArgumentException("Expected a String key but received $key") + } + }.toMap() +) + actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> mutableListOf() .also { value = it } diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 0497ec0d7..363ecbaa6 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -11,6 +11,10 @@ import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule +expect class EncodedObject + +expect val emptyEncodedObject: EncodedObject + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(strategy, value) { encodeDefaults = shouldEncodeElementDefault }")) fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = encode(strategy, value) { this.encodeDefaults = shouldEncodeElementDefault @@ -31,6 +35,23 @@ inline fun encode(value: T, shouldEncodeElementDefault: Boolean): An inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) +inline fun encodeAsObject(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { + val encoded = encode(strategy, value, buildSettings) + return when (encoded) { + is Map<*, *> -> encoded.asEncodedObject() + null -> throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + else -> throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") + } +} +inline fun encodeAsObject(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { + val encoded = encode(value, buildSettings) + return when (encoded) { + is Map<*, *> -> encoded.asEncodedObject() + null -> throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + else -> throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") + } +} + @PublishedApi internal inline fun encode(value: T, encodeSettings: EncodeSettings): Any? = value?.let { FirebaseEncoder(encodeSettings).apply { @@ -45,6 +66,9 @@ internal inline fun encode(value: T, encodeSettings: EncodeSettings) }.value } +@PublishedApi +internal expect fun Map<*, *>.asEncodedObject(): EncodedObject + /** * An extension which which serializer to use for value. Handy in updating fields by name or path * where using annotation is not possible diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 461cc62cd..e393ebf56 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -74,13 +74,15 @@ data class NestedClass( class EncodersTest { @Test - fun encodeDecodeNullableString() { - val encoded = encode(null) { encodeDefaults = true } - - nativeAssertEquals(null, encoded) - - val decoded = decode(String.serializer().nullable, encoded) - assertNull(decoded) + fun encodeDecodePrimaryTypes() { + assertEncode(true) + assertEncode(42) + assertEncode(8.toShort()) + assertEncode(Int.MAX_VALUE.toLong() + 3) + assertEncode(0x03F) + assertEncode(3.33) + assertEncode(6.65f) + assertEncode("Test") } @Test fun encodeDecodeList() { @@ -413,4 +415,14 @@ class EncodersTest { reencoded ) } + + private inline fun assertEncode(value: T) { + val encoded = encode(value) + assertEquals(value, encoded) + assertEquals(value, decode(encoded)) + + val nullableEncoded = encode(null) + assertNull(nullableEncoded) + assertNull(decode(nullableEncoded)) + } } diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt index 144b0e60f..58cecc4d9 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -9,6 +9,18 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set +actual data class EncodedObject( + val ios: Map +) + +actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) + +@PublishedApi +internal actual fun Map<*, *>.asEncodedObject() = EncodedObject( + map { (key, value) -> + key to value + }.toMap() +) actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> encodeAsList() StructureKind.MAP -> mutableListOf() diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index dbb3d1f15..547f8b5af 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -7,8 +7,22 @@ package dev.gitlive.firebase import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind +import kotlin.js.Json import kotlin.js.json +actual typealias EncodedObject = Json + +actual val emptyEncodedObject: EncodedObject = json() +@PublishedApi +internal actual fun Map<*, *>.asEncodedObject() = json( + *map { (key, value) -> + if (key is String) { + key to value + } else { + throw IllegalArgumentException("Expected a String key but received $key") + } + }.toTypedArray() +) actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> encodeAsList(descriptor) StructureKind.MAP -> { diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index 772bbd900..9f2a4a0e5 100644 --- a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -13,6 +13,7 @@ import com.google.firebase.database.Transaction import com.google.firebase.database.ValueEventListener import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type @@ -205,9 +206,8 @@ internal actual class NativeDatabaseReference internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateEncodedChildren(encodedUpdate: Any?) = - android.updateChildren(encodedUpdate as Map) + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = + android.updateChildren(encodedUpdate.android) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @@ -253,7 +253,6 @@ internal actual class NativeDatabaseReference internal constructor( val DatabaseReference.android get() = nativeReference.android -@Suppress("UNCHECKED_CAST") actual class DataSnapshot internal constructor( val android: com.google.firebase.database.DataSnapshot, private val persistenceEnabled: Boolean @@ -297,9 +296,8 @@ internal actual class NativeOnDisconnect internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateEncodedChildren(encodedUpdate: Any?) = - android.updateChildren(encodedUpdate as Map) + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = + android.updateChildren(encodedUpdate.android) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } } diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index 1236d8608..7d46135d0 100644 --- a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -7,6 +7,7 @@ package dev.gitlive.firebase.database import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeDecodeSettingsBuilder import dev.gitlive.firebase.EncodeSettings +import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type.ADDED @@ -14,6 +15,7 @@ import dev.gitlive.firebase.database.ChildEvent.Type.CHANGED import dev.gitlive.firebase.database.ChildEvent.Type.MOVED import dev.gitlive.firebase.database.ChildEvent.Type.REMOVED import dev.gitlive.firebase.encode +import dev.gitlive.firebase.encodeAsObject import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer @@ -82,7 +84,7 @@ internal expect class NativeDatabaseReference : NativeQuery { val key: String? fun push(): NativeDatabaseReference suspend fun setValueEncoded(encodedValue: Any?) - suspend fun updateEncodedChildren(encodedUpdate: Any?) + suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) fun child(path: String): NativeDatabaseReference fun onDisconnect(): NativeOnDisconnect @@ -118,7 +120,7 @@ class DatabaseReference internal constructor(@PublishedApi internal val nativeRe this.encodeDefaults = encodeDefaults } suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = nativeReference.updateEncodedChildren( - encode(update, buildSettings)) + encodeAsObject(update, buildSettings)) suspend fun removeValue() = nativeReference.removeValue() @@ -144,7 +146,7 @@ internal expect class NativeOnDisconnect { suspend fun removeValue() suspend fun cancel() suspend fun setValue(encodedValue: Any?) - suspend fun updateEncodedChildren(encodedUpdate: Any?) + suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) } class OnDisconnect internal constructor(@PublishedApi internal val native: NativeOnDisconnect) { @@ -160,7 +162,9 @@ class OnDisconnect internal constructor(@PublishedApi internal val native: Nativ setValue(strategy, value) { this.encodeDefaults = encodeDefaults } suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValue(encode(strategy, value, buildSettings)) - suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncodedChildren(encode(update, buildSettings)) + suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncodedChildren( + encodeAsObject(update, buildSettings) + ) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { this.encodeDefaults = encodeDefaults }")) suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update) { this.encodeDefaults = encodeDefaults diff --git a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt index e2042acab..d53746b25 100644 --- a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -16,6 +16,7 @@ import cocoapods.FirebaseDatabase.FIRDatabaseReference import cocoapods.FirebaseDatabase.FIRTransactionResult import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type @@ -168,9 +169,8 @@ internal actual class NativeDatabaseReference internal constructor( ios.await(persistenceEnabled) { setValue(encodedValue, it) } } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateEncodedChildren(encodedUpdate: Any?) { - ios.await(persistenceEnabled) { updateChildValues(encodedUpdate as Map, it) } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { + ios.await(persistenceEnabled) { updateChildValues(encodedUpdate.ios, it) } } actual suspend fun removeValue() { @@ -199,7 +199,6 @@ internal actual class NativeDatabaseReference internal constructor( val DatabaseReference.ios: FIRDatabaseReference get() = nativeReference.ios -@Suppress("UNCHECKED_CAST") actual class DataSnapshot internal constructor( val ios: FIRDataSnapshot, private val persistenceEnabled: Boolean @@ -241,9 +240,8 @@ internal actual class NativeOnDisconnect internal constructor( ios.await(persistenceEnabled) { onDisconnectSetValue(encodedValue, it) } } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateEncodedChildren(encodedUpdate: Any?) { - ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate as Map, it) } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { + ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate.ios, it) } } } diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index dea7e9288..c67edf051 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -6,6 +6,7 @@ package dev.gitlive.firebase.database import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.externals.CancelCallback @@ -182,8 +183,8 @@ internal actual class NativeDatabaseReference internal constructor( set(js, encodedValue).awaitWhileOnline(database) } - actual suspend fun updateEncodedChildren(encodedUpdate: Any?) = - rethrow { update(js, encodedUpdate ?: json()).awaitWhileOnline(database) } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = + rethrow { update(js, encodedUpdate).awaitWhileOnline(database) } actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { @@ -234,8 +235,8 @@ internal actual class NativeOnDisconnect internal constructor( actual suspend fun setValue(encodedValue: Any?) = rethrow { js.set(encodedValue).awaitWhileOnline(database) } - actual suspend fun updateEncodedChildren(encodedUpdate: Any?) = - rethrow { js.update(encodedUpdate ?: json()).awaitWhileOnline(database) } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = + rethrow { js.update(encodedUpdate).awaitWhileOnline(database) } } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index fcfe4056b..1e7a557f8 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -6,6 +6,7 @@ package dev.gitlive.firebase.firestore import com.google.firebase.firestore.MetadataChanges +import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import kotlinx.coroutines.channels.awaitClose @@ -13,7 +14,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.runBlocking import kotlinx.coroutines.tasks.await -import kotlinx.serialization.Serializable import com.google.firebase.firestore.FieldPath as AndroidFieldPath import com.google.firebase.firestore.Filter as AndroidFilter import com.google.firebase.firestore.Query as AndroidQuery @@ -26,19 +26,19 @@ actual fun Firebase.firestore(app: FirebaseApp) = actual class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) { - actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReference(android.collection(collectionPath))) + actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReferenceWrapper(android.collection(collectionPath))) actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId).native) actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(android.document(documentPath))) - actual fun batch() = WriteBatch(NativeWriteBatch(android.batch())) + actual fun batch() = WriteBatch(NativeWriteBatchWrapper(android.batch())) actual fun setLoggingEnabled(loggingEnabled: Boolean) = com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) actual suspend fun runTransaction(func: suspend Transaction.() -> T): T = - android.runTransaction { runBlocking { Transaction(NativeTransaction(it)).func() } }.await() + android.runTransaction { runBlocking { Transaction(NativeTransactionWrapper(it)).func() } }.await() actual suspend fun clearPersistence() = android.clearPersistence().await().run { } @@ -75,13 +75,13 @@ internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() } @PublishedApi -internal actual class NativeWriteBatch(val android: com.google.firebase.firestore.WriteBatch) { +internal actual class NativeWriteBatchWrapper(val android: com.google.firebase.firestore.WriteBatch) { actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): NativeWriteBatch = (setOptions.android?.let { + ): NativeWriteBatchWrapper = (setOptions.android?.let { android.set(documentRef.android, encodedData, it) } ?: android.set(documentRef.android, encodedData)).let { this @@ -115,21 +115,20 @@ internal actual class NativeWriteBatch(val android: com.google.firebase.firestor val WriteBatch.android get() = native.android @PublishedApi -internal actual class NativeTransaction(val android: com.google.firebase.firestore.Transaction) { +internal actual class NativeTransactionWrapper(val android: com.google.firebase.firestore.Transaction) { actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): NativeTransaction { + ): NativeTransactionWrapper { setOptions.android?.let { android.set(documentRef.android, encodedData, it) } ?: android.set(documentRef.android, encodedData) return this } - @Suppress("UNCHECKED_CAST") - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any) = android.update(documentRef.android, encodedData as Map).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = android.update(documentRef.android, encodedData.android).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, @@ -149,10 +148,10 @@ internal actual class NativeTransaction(val android: com.google.firebase.firesto android.delete(documentRef.android).let { this } actual suspend fun get(documentRef: DocumentReference) = - NativeDocumentSnapshot(android.get(documentRef.android)) + NativeDocumentSnapshotWrapper(android.get(documentRef.android)) } -val Transaction.android get() = native.android +val Transaction.android get() = nativeWrapper.android /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = com.google.firebase.firestore.DocumentReference @@ -166,24 +165,23 @@ internal actual class NativeDocumentReference actual constructor(actual val nati actual val path: String get() = android.path - actual val parent: NativeCollectionReference - get() = NativeCollectionReference(android.parent) + actual val parent: NativeCollectionReferenceWrapper + get() = NativeCollectionReferenceWrapper(android.parent) - actual fun collection(collectionPath: String) = NativeCollectionReference(android.collection(collectionPath)) + actual fun collection(collectionPath: String) = NativeCollectionReferenceWrapper(android.collection(collectionPath)) actual suspend fun get() = - NativeDocumentSnapshot(android.get().await()) + NativeDocumentSnapshotWrapper(android.get().await()) - actual suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) { + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) { val task = (setOptions.android?.let { - android.set(encodedData, it) - } ?: android.set(encodedData)) + android.set(encodedData.android, it) + } ?: android.set(encodedData.android)) task.await() } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateEncoded(encodedData: Any) { - android.update(encodedData as Map).await() + actual suspend fun updateEncoded(encodedData: EncodedObject) { + android.update(encodedData.android).await() } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { @@ -203,12 +201,12 @@ internal actual class NativeDocumentReference actual constructor(actual val nati android.delete().await() } - actual val snapshots: Flow get() = snapshots() + actual val snapshots: Flow get() = snapshots() actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(NativeDocumentSnapshot(snapshot)) } + snapshot?.let { trySend(NativeDocumentSnapshotWrapper(snapshot)) } exception?.let { close(exception) } } awaitClose { listener.remove() } @@ -223,16 +221,16 @@ internal actual class NativeDocumentReference actual constructor(actual val nati val DocumentReference.android get() = native.android @PublishedApi -internal actual open class NativeQuery(open val android: AndroidQuery) -internal val AndroidQuery.native get() = NativeQuery(this) +internal actual open class NativeQueryWrapper(open val android: AndroidQuery) +internal val AndroidQuery.native get() = NativeQueryWrapper(this) -actual open class Query internal actual constructor(nativeQuery: NativeQuery) { +actual open class Query internal actual constructor(nativeWrapper: NativeQueryWrapper) { - open val android = nativeQuery.android + open val android = nativeWrapper.android actual suspend fun get() = QuerySnapshot(android.get().await()) - actual fun limit(limit: Number) = Query(NativeQuery(android.limit(limit.toLong()))) + actual fun limit(limit: Number) = Query(NativeQueryWrapper(android.limit(limit.toLong()))) actual val snapshots get() = callbackFlow { val listener = android.addSnapshotListener { snapshot, exception -> @@ -336,7 +334,7 @@ actual typealias Direction = com.google.firebase.firestore.Query.Direction actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type @PublishedApi -internal actual class NativeCollectionReference(override val android: com.google.firebase.firestore.CollectionReference) : NativeQuery(android) { +internal actual class NativeCollectionReferenceWrapper(override val android: com.google.firebase.firestore.CollectionReference) : NativeQueryWrapper(android) { actual val path: String get() = android.path @@ -352,7 +350,7 @@ internal actual class NativeCollectionReference(override val android: com.google actual suspend fun addEncoded(data: Any) = NativeDocumentReference(android.add(data).await()) } -val CollectionReference.android get() = native.android +val CollectionReference.android get() = nativeWrapper.android actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException @@ -362,7 +360,7 @@ actual typealias FirestoreExceptionCode = com.google.firebase.firestore.Firebase actual class QuerySnapshot(val android: com.google.firebase.firestore.QuerySnapshot) { actual val documents - get() = android.documents.map { DocumentSnapshot(NativeDocumentSnapshot(it)) } + get() = android.documents.map { DocumentSnapshot(NativeDocumentSnapshotWrapper(it)) } actual val documentChanges get() = android.documentChanges.map { DocumentChange(it) } actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.metadata) @@ -370,7 +368,7 @@ actual class QuerySnapshot(val android: com.google.firebase.firestore.QuerySnaps actual class DocumentChange(val android: com.google.firebase.firestore.DocumentChange) { actual val document: DocumentSnapshot - get() = DocumentSnapshot(NativeDocumentSnapshot(android.document)) + get() = DocumentSnapshot(NativeDocumentSnapshotWrapper(android.document)) actual val newIndex: Int get() = android.newIndex actual val oldIndex: Int @@ -380,12 +378,13 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC } @PublishedApi -internal actual class NativeDocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) { +internal actual class NativeDocumentSnapshotWrapper(val android: com.google.firebase.firestore.DocumentSnapshot) { actual val id get() = android.id actual val reference get() = NativeDocumentReference(android.reference) actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = android.get(field, serverTimestampBehavior.toAndroid()) + actual fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = android.get(fieldPath.encoded, serverTimestampBehavior.toAndroid()) actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = android.getData(serverTimestampBehavior.toAndroid()) actual fun contains(field: String) = android.contains(field) @@ -401,7 +400,7 @@ internal actual class NativeDocumentSnapshot(val android: com.google.firebase.fi } } -val DocumentSnapshot.android get() = native.android +val DocumentSnapshot.android get() = nativeWrapper.android actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { actual val hasPendingWrites: Boolean get() = android.hasPendingWrites() diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 5a00e0d4d..88915b86e 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -42,69 +42,77 @@ internal sealed class SetOptions { } } +expect class NativeTransaction @PublishedApi -internal expect class NativeTransaction { - fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): NativeTransaction - fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransaction - fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransaction - fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransaction - fun delete(documentRef: DocumentReference): NativeTransaction - suspend fun get(documentRef: DocumentReference): NativeDocumentSnapshot +internal expect class NativeTransactionWrapper internal constructor(native: NativeTransaction) { + + val native: NativeTransaction + + fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeTransactionWrapper + fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper + fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper + fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper + fun delete(documentRef: DocumentReference): NativeTransactionWrapper + suspend fun get(documentRef: DocumentReference): NativeDocumentSnapshotWrapper } -data class Transaction internal constructor(@PublishedApi internal val native: NativeTransaction) { +data class Transaction internal constructor(@PublishedApi internal val nativeWrapper: NativeTransactionWrapper) { + + constructor(native: NativeTransaction) : this(NativeTransactionWrapper(native)) + + val native = nativeWrapper.native @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults }")) fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean = false): Transaction = set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, data: Any, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(documentRef: DocumentReference, data: Any, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, *mergeFields) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, *mergeFieldPaths) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, merge) { this.encodeDefaults = encodeDefaults }")) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, merge) { + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, merge) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, *mergeFields) { + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, *mergeFields) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, *mergeFieldPaths) { + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, *mergeFieldPaths) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @PublishedApi - internal fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): Transaction = Transaction(native.setEncoded(documentRef, encodedData, setOptions)) + internal fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): Transaction = Transaction(nativeWrapper.setEncoded(documentRef, encodedData, setOptions)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { this.encodeDefaults = encodeDefaults }")) fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = update(documentRef, data) { this.encodeDefaults = encodeDefaults } - inline fun update(documentRef: DocumentReference, data: Any, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encode(data, buildSettings)!!) + inline fun update(documentRef: DocumentReference, data: Any, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encodeAsObject(data, buildSettings)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, strategy, data) { this.encodeDefaults = encodeDefaults }")) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data) { + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data) { this.encodeDefaults = encodeDefaults } - inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encode(strategy, data, buildSettings)!!) + inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encodeAsObject(strategy, data, buildSettings)) @JvmName("updateFields") inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @@ -112,16 +120,16 @@ data class Transaction internal constructor(@PublishedApi internal val native: N inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @PublishedApi - internal fun updateEncoded(documentRef: DocumentReference, encodedData: Any): Transaction = Transaction(native.updateEncoded(documentRef, encodedData)) + internal fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): Transaction = Transaction(nativeWrapper.updateEncoded(documentRef, encodedData)) @PublishedApi - internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): Transaction = Transaction(native.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) + internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): Transaction = Transaction(nativeWrapper.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) @PublishedApi - internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): Transaction = Transaction(native.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) + internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): Transaction = Transaction(nativeWrapper.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) - fun delete(documentRef: DocumentReference): Transaction = Transaction(native.delete(documentRef)) - suspend fun get(documentRef: DocumentReference): DocumentSnapshot = DocumentSnapshot(native.get(documentRef)) + fun delete(documentRef: DocumentReference): Transaction = Transaction(nativeWrapper.delete(documentRef)) + suspend fun get(documentRef: DocumentReference): DocumentSnapshot = DocumentSnapshot(nativeWrapper.get(documentRef)) } @PublishedApi @@ -225,76 +233,80 @@ internal val Any.safeValue: Any get() = when (this) { else -> this } +expect class NativeWriteBatch + @PublishedApi -internal expect class NativeWriteBatch { - fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): NativeWriteBatch - fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatch - fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatch - fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatch - fun delete(documentRef: DocumentReference): NativeWriteBatch +internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch){ + fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeWriteBatchWrapper + fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper + fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper + fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper + fun delete(documentRef: DocumentReference): NativeWriteBatchWrapper suspend fun commit() } -data class WriteBatch internal constructor(@PublishedApi internal val native: NativeWriteBatch) { +data class WriteBatch internal constructor(@PublishedApi internal val native: NativeWriteBatchWrapper) { + + constructor(native: NativeWriteBatch) : this(NativeWriteBatchWrapper(native)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults }")) - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, data, merge) { + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - setEncoded(documentRef, encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(documentRef: DocumentReference, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setEncoded(documentRef, encodeAsObject(data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, *mergeFields) { + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, *mergeFields) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(documentRef: DocumentReference, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setEncoded(documentRef, encodeAsObject(data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, *mergeFieldPaths) { + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, *mergeFieldPaths) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + inline fun set(documentRef: DocumentReference, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setEncoded(documentRef, encodeAsObject(data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, merge) { this.encodeDefaults = encodeDefaults }")) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, merge) { + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, merge) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - setEncoded(documentRef, encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, *mergeFields){ + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, *mergeFields){ this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, *mergeFieldPaths) { + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, *mergeFieldPaths) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @PublishedApi - internal fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions) = WriteBatch(native.setEncoded(documentRef, encodedData, setOptions)) + internal fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions) = WriteBatch(native.setEncoded(documentRef, encodedData, setOptions)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { this.encodeDefaults = encodeDefaults }")) - inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = update(documentRef, data) { + inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = update(documentRef, data) { this.encodeDefaults = encodeDefaults } - inline fun update(documentRef: DocumentReference, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - updateEncoded(documentRef, encode(data, buildSettings)!!) + inline fun update(documentRef: DocumentReference, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + updateEncoded(documentRef, encodeAsObject(data, buildSettings)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, strategy, data) { this.encodeDefaults = encodeDefaults }")) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data) { + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data) { this.encodeDefaults = encodeDefaults } - inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - updateEncoded(documentRef, encode(strategy, data, buildSettings)!!) + inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + updateEncoded(documentRef, encodeAsObject(strategy, data, buildSettings)) @JvmName("updateField") inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @@ -302,7 +314,7 @@ data class WriteBatch internal constructor(@PublishedApi internal val native: Na inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @PublishedApi - internal fun updateEncoded(documentRef: DocumentReference, encodedData: Any) = WriteBatch(native.updateEncoded(documentRef, encodedData)) + internal fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = WriteBatch(native.updateEncoded(documentRef, encodedData)) @PublishedApi internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(native.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) @@ -322,14 +334,14 @@ internal expect class NativeDocumentReference(nativeValue: NativeDocumentReferen val nativeValue: NativeDocumentReferenceType val id: String val path: String - val snapshots: Flow - val parent: NativeCollectionReference - fun snapshots(includeMetadataChanges: Boolean = false): Flow - - fun collection(collectionPath: String): NativeCollectionReference - suspend fun get(): NativeDocumentSnapshot - suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) - suspend fun updateEncoded(encodedData: Any) + val snapshots: Flow + val parent: NativeCollectionReferenceWrapper + fun snapshots(includeMetadataChanges: Boolean = false): Flow + + fun collection(collectionPath: String): NativeCollectionReferenceWrapper + suspend fun get(): NativeDocumentSnapshotWrapper + suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) + suspend fun updateEncoded(encodedData: EncodedObject) suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) suspend fun delete() @@ -351,55 +363,60 @@ data class DocumentReference internal constructor(@PublishedApi internal val nat suspend fun get(): DocumentSnapshot = DocumentSnapshot(native.get()) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, merge) { this.encodeDefaults = encodeDefaults }")) - suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(data, merge) { + suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(data, merge) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded(encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + suspend inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded( + encodeAsObject(data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFields) { this.encodeDefaults = encodeDefaults }")) - suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(data, *mergeFields) { + suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(data, *mergeFields) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + suspend inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded( + encodeAsObject(data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) - suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(data, *mergeFieldPaths) { + suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(data, *mergeFieldPaths) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + suspend inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded( + encodeAsObject(data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, merge) { this.encodeDefaults = encodeDefaults }")) - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(strategy, data, merge) { + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(strategy, data, merge) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded( - encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + suspend inline fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded( + encodeAsObject(strategy, data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(strategy, data, *mergeFields) { + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(strategy, data, *mergeFields) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded( - encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded( + encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(strategy, data, *mergeFieldPaths) { + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(strategy, data, *mergeFieldPaths) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded( - encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded( + encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(data) { this.encodeDefaults = encodeDefaults }")) - suspend inline fun update(data: T, encodeDefaults: Boolean) = update(data) { + suspend inline fun update(data: T, encodeDefaults: Boolean) = update(data) { this.encodeDefaults = encodeDefaults } - suspend inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncoded(encode(data, buildSettings)!!) + suspend inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncoded(encodeAsObject(data, buildSettings)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(strategy, data) { this.encodeDefaults = encodeDefaults }")) - suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(strategy, data) { + suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(strategy, data) { this.encodeDefaults = encodeDefaults } - suspend inline fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncoded(encode(strategy, data, buildSettings)!!) + suspend inline fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncoded( + encodeAsObject(strategy, data, buildSettings) + ) @JvmName("updateFields") suspend inline fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @@ -410,42 +427,51 @@ data class DocumentReference internal constructor(@PublishedApi internal val nat suspend fun delete() = native.delete() } +expect class NativeCollectionReference + @PublishedApi -internal expect class NativeCollectionReference : NativeQuery { +internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : NativeQuery { + + val native: NativeCollectionReference + val path: String val document: NativeDocumentReference val parent: NativeDocumentReference? fun document(documentPath: String): NativeDocumentReference - suspend fun addEncoded(data: Any): NativeDocumentReference + suspend fun addEncoded(data: EncodedObject): NativeDocumentReference } -data class CollectionReference internal constructor(@PublishedApi internal val native: NativeCollectionReference) : Query(native) { +data class CollectionReference internal constructor(@PublishedApi internal val nativeWrapper: NativeCollectionReferenceWrapper) : Query(nativeWrapper) { - val path: String get() = native.path - val document: DocumentReference get() = DocumentReference(native.document) - val parent: DocumentReference? get() = native.parent?.let(::DocumentReference) + constructor(native: NativeCollectionReference) : this(NativeCollectionReferenceWrapper(native)) - fun document(documentPath: String): DocumentReference = DocumentReference(native.document(documentPath)) + val native = nativeWrapper.native + + val path: String get() = nativeWrapper.path + val document: DocumentReference get() = DocumentReference(nativeWrapper.document) + val parent: DocumentReference? get() = nativeWrapper.parent?.let(::DocumentReference) + + fun document(documentPath: String): DocumentReference = DocumentReference(nativeWrapper.document(documentPath)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(data) { this.encodeDefaults = encodeDefaults }")) - suspend inline fun add(data: T, encodeDefaults: Boolean) = add(data) { + suspend inline fun add(data: T, encodeDefaults: Boolean) = add(data) { this.encodeDefaults = encodeDefaults } - suspend inline fun add(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( - encode(data, buildSettings)!! + suspend inline fun add(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( + encodeAsObject(data, buildSettings) ) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(strategy, data) { this.encodeDefaults = encodeDefaults }")) - suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = add(strategy, data) { + suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = add(strategy, data) { this.encodeDefaults = encodeDefaults } - suspend inline fun add(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( - encode(strategy, data, buildSettings)!! + suspend inline fun add(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( + encodeAsObject(strategy, data, buildSettings) ) @PublishedApi - internal suspend fun addEncoded(data: Any): DocumentReference = DocumentReference(native.addEncoded(data)) + internal suspend fun addEncoded(data: EncodedObject): DocumentReference = DocumentReference(nativeWrapper.addEncoded(data)) } expect class FirebaseFirestoreException : FirebaseException @@ -497,8 +523,11 @@ expect class DocumentChange { val type: ChangeType } +expect class NativeDocumentSnapshot @PublishedApi -internal expect class NativeDocumentSnapshot { +internal expect class NativeDocumentSnapshotWrapper internal constructor(native: NativeDocumentSnapshot) { + + val native: NativeDocumentSnapshot val exists: Boolean val id: String @@ -506,30 +535,45 @@ internal expect class NativeDocumentSnapshot { val metadata: SnapshotMetadata fun contains(field: String): Boolean + fun contains(fieldPath: FieldPath): Boolean fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? } -data class DocumentSnapshot internal constructor(@PublishedApi internal val native: NativeDocumentSnapshot) { +data class DocumentSnapshot internal constructor(@PublishedApi internal val nativeWrapper: NativeDocumentSnapshotWrapper) { - val exists: Boolean get() = native.exists - val id: String get() = native.id - val reference: DocumentReference get() = DocumentReference(native.reference) - val metadata: SnapshotMetadata get() = native.metadata + constructor(native: NativeDocumentSnapshot) : this(NativeDocumentSnapshotWrapper(native)) + + val native = nativeWrapper.native + val exists: Boolean get() = nativeWrapper.exists + val id: String get() = nativeWrapper.id + val reference: DocumentReference get() = DocumentReference(nativeWrapper.reference) + val metadata: SnapshotMetadata get() = nativeWrapper.metadata + + fun contains(field: String): Boolean = nativeWrapper.contains(field) + fun contains(fieldPath: FieldPath): Boolean = nativeWrapper.contains(fieldPath) inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(value = getEncoded(field, serverTimestampBehavior), buildSettings) inline fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(field, serverTimestampBehavior), buildSettings) @PublishedApi - internal fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = native.getEncoded(field, serverTimestampBehavior) + internal fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.getEncoded(field, serverTimestampBehavior) + + inline fun get(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(value = getEncoded(fieldPath, serverTimestampBehavior), buildSettings) + inline fun get(fieldPath: FieldPath, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(fieldPath, serverTimestampBehavior), buildSettings) + + @PublishedApi + internal fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.getEncoded(fieldPath, serverTimestampBehavior) + - inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(encodedData(serverTimestampBehavior), buildSettings) + inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(encodedData(serverTimestampBehavior), buildSettings) inline fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, encodedData(serverTimestampBehavior), buildSettings) @PublishedApi - internal fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = native.encodedData(serverTimestampBehavior) + internal fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.encodedData(serverTimestampBehavior) } enum class ServerTimestampBehavior { diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/serializers.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/serializers.kt deleted file mode 100644 index 5916bce4c..000000000 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/serializers.kt +++ /dev/null @@ -1,28 +0,0 @@ -package dev.gitlive.firebase.firestore - -import kotlinx.serialization.descriptors.ClassSerialDescriptorBuilder -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.nullable - -/** - * Builder for a [SerialDescriptor] which fixes an nullability issue in [kotlinx.serialization.descriptors.buildClassSerialDescriptor] - * @return a class [SerialDescriptor]. */ -fun buildClassSerialDescriptor( - serialName: String, - vararg typeParameters: SerialDescriptor, - isNullable: Boolean, - builderAction: ClassSerialDescriptorBuilder.() -> Unit = {} -): SerialDescriptor { - val descriptor = kotlinx.serialization.descriptors.buildClassSerialDescriptor( - serialName = serialName, - typeParameters = typeParameters, - builderAction = builderAction - ) - - return if (isNullable && !descriptor.isNullable) { - // bug https://github.com/Kotlin/kotlinx.serialization/issues/1929 - descriptor.nullable - } else { - descriptor - } -} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 2dc2890a7..2689c0950 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -9,13 +9,9 @@ import cocoapods.FirebaseFirestoreInternal.FIRDocumentChangeType.* import dev.gitlive.firebase.* import kotlinx.cinterop.* import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.runBlocking -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationStrategy import platform.Foundation.NSError import platform.Foundation.NSNull @@ -26,22 +22,21 @@ actual fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore = FirebaseFir FIRFirestore.firestoreForApp(app.ios as objcnames.classes.FIRApp) ) -@Suppress("UNCHECKED_CAST") actual class FirebaseFirestore(val ios: FIRFirestore) { - actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReference(ios.collectionWithPath(collectionPath))) + actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReferenceWrapper(ios.collectionWithPath(collectionPath))) actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId).native) actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(ios.documentWithPath(documentPath))) - actual fun batch() = WriteBatch(NativeWriteBatch(ios.batch())) + actual fun batch() = WriteBatch(NativeWriteBatchWrapper(ios.batch())) actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = FIRFirestore.enableLogging(loggingEnabled) actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - awaitResult { ios.runTransactionWithBlock({ transaction, _ -> runBlocking { Transaction(NativeTransaction(transaction!!)).func() } }, it) } as T + awaitResult { ios.runTransactionWithBlock({ transaction, _ -> runBlocking { Transaction(NativeTransactionWrapper(transaction!!)).func() } }, it) } as T actual suspend fun clearPersistence() = await { ios.clearPersistenceWithCompletion(it) } @@ -72,27 +67,28 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { } } -@Suppress("UNCHECKED_CAST") +actual typealias NativeWriteBatch = FIRWriteBatch + @PublishedApi -internal actual class NativeWriteBatch(val ios: FIRWriteBatch) { +internal actual class NativeWriteBatchWrapper actual constructor(val native: FIRWriteBatch) { actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions - ): NativeWriteBatch = when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData as Map, documentRef.ios, true) - is SetOptions.Overwrite -> ios.setData(encodedData as Map, documentRef.ios, false) - is SetOptions.MergeFields -> ios.setData(encodedData as Map, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData as Map, documentRef.ios, setOptions.encodedFieldPaths) + ): NativeWriteBatchWrapper = when (setOptions) { + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatch = ios.updateData(encodedData as Map, documentRef.ios).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): NativeWriteBatch = ios.updateData( + ): NativeWriteBatchWrapper = native.updateData( encodedFieldsAndValues.toMap(), documentRef.ios ).let { this } @@ -100,40 +96,41 @@ internal actual class NativeWriteBatch(val ios: FIRWriteBatch) { actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): NativeWriteBatch = ios.updateData( + ): NativeWriteBatchWrapper = native.updateData( encodedFieldsAndValues.toMap(), documentRef.ios ).let { this } actual fun delete(documentRef: DocumentReference) = - ios.deleteDocument(documentRef.ios).let { this } + native.deleteDocument(documentRef.ios).let { this } - actual suspend fun commit() = await { ios.commitWithCompletion(it) } + actual suspend fun commit() = await { native.commitWithCompletion(it) } } -val WriteBatch.ios get() = native.ios +val WriteBatch.ios get() = native.native + +actual typealias NativeTransaction = FIRTransaction -@Suppress("UNCHECKED_CAST") @PublishedApi -internal actual class NativeTransaction(val ios: FIRTransaction) { +internal actual class NativeTransactionWrapper actual constructor(actual val native: FIRTransaction) { actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions - ): NativeTransaction = when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData as Map, documentRef.ios, true) - is SetOptions.Overwrite -> ios.setData(encodedData as Map, documentRef.ios, false) - is SetOptions.MergeFields -> ios.setData(encodedData as Map, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData as Map, documentRef.ios, setOptions.encodedFieldPaths) + ): NativeTransactionWrapper = when (setOptions) { + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransaction = ios.updateData(encodedData as Map, documentRef.ios).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): NativeTransaction = ios.updateData( + ): NativeTransactionWrapper = native.updateData( encodedFieldsAndValues.toMap(), documentRef.ios ).let { this } @@ -141,31 +138,30 @@ internal actual class NativeTransaction(val ios: FIRTransaction) { actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): NativeTransaction = ios.updateData( + ): NativeTransactionWrapper = native.updateData( encodedFieldsAndValues.toMap(), documentRef.ios ).let { this } actual fun delete(documentRef: DocumentReference) = - ios.deleteDocument(documentRef.ios).let { this } + native.deleteDocument(documentRef.ios).let { this } actual suspend fun get(documentRef: DocumentReference) = - throwError { NativeDocumentSnapshot(ios.getDocument(documentRef.ios, it)!!) } + throwError { NativeDocumentSnapshotWrapper(native.getDocument(documentRef.ios, it)!!) } } -val Transaction.ios get() = native.ios +val Transaction.ios get() = nativeWrapper.native /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = FIRDocumentReference -@Suppress("UNCHECKED_CAST") @PublishedApi internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> - snapshot?.let { trySend(NativeDocumentSnapshot(snapshot)) } + snapshot?.let { trySend(NativeDocumentSnapshotWrapper(snapshot)) } error?.let { close(error.toException()) } } awaitClose { listener.remove() } @@ -179,26 +175,26 @@ internal actual class NativeDocumentReference actual constructor(actual val nati actual val path: String get() = ios.path - actual val parent: NativeCollectionReference - get() = NativeCollectionReference(ios.parent) + actual val parent: NativeCollectionReferenceWrapper + get() = NativeCollectionReferenceWrapper(ios.parent) - actual fun collection(collectionPath: String) = NativeCollectionReference(ios.collectionWithPath(collectionPath)) + actual fun collection(collectionPath: String) = NativeCollectionReferenceWrapper(ios.collectionWithPath(collectionPath)) actual suspend fun get() = - NativeDocumentSnapshot(awaitResult { ios.getDocumentWithCompletion(it) }) + NativeDocumentSnapshotWrapper(awaitResult { ios.getDocumentWithCompletion(it) }) - actual suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) = await { + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = await { when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData as Map, true, it) - is SetOptions.Overwrite -> ios.setData(encodedData as Map, false, it) - is SetOptions.MergeFields -> ios.setData(encodedData as Map, setOptions.fields, it) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData as Map, setOptions.encodedFieldPaths, it) + is SetOptions.Merge -> ios.setData(encodedData.ios, true, it) + is SetOptions.Overwrite -> ios.setData(encodedData.ios, false, it) + is SetOptions.MergeFields -> ios.setData(encodedData.ios, setOptions.fields, it) + is SetOptions.MergeFieldPaths -> ios.setData(encodedData.ios, setOptions.encodedFieldPaths, it) } } - actual suspend fun updateEncoded(encodedData: Any) = await { - ios.updateData(encodedData as Map, it) + actual suspend fun updateEncoded(encodedData: EncodedObject) = await { + ios.updateData(encodedData.ios, it) } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = await { @@ -211,9 +207,9 @@ internal actual class NativeDocumentReference actual constructor(actual val nati actual suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } - actual val snapshots get() = callbackFlow { + actual val snapshots get() = callbackFlow { val listener = ios.addSnapshotListener { snapshot, error -> - snapshot?.let { trySend(NativeDocumentSnapshot(snapshot)) } + snapshot?.let { trySend(NativeDocumentSnapshotWrapper(snapshot)) } error?.let { close(error.toException()) } } awaitClose { listener.remove() } @@ -303,9 +299,12 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } -@Suppress("UNCHECKED_CAST") +actual typealias NativeCollectionReference = FIRCollectionReference + @PublishedApi -internal actual class NativeCollectionReference(override val ios: FIRCollectionReference) : NativeQuery(ios) { +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual val native: NativeCollectionReference) : NativeQuery(native) { + + override val ios: FIRCollectionReference = native actual val path: String get() = ios.path @@ -316,10 +315,10 @@ internal actual class NativeCollectionReference(override val ios: FIRCollectionR actual fun document(documentPath: String) = NativeDocumentReference(ios.documentWithPath(documentPath)) - actual suspend fun addEncoded(data: Any) = NativeDocumentReference(await { ios.addDocumentWithData(data as Map, it) }) + actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { ios.addDocumentWithData(data.ios, it) }) } -val CollectionReference.ios get() = native.ios +val CollectionReference.ios get() = nativeWrapper.ios actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) @@ -382,7 +381,7 @@ fun NSError.toException() = when(domain) { actual class QuerySnapshot(val ios: FIRQuerySnapshot) { actual val documents - get() = ios.documents.map { DocumentSnapshot(NativeDocumentSnapshot(it as FIRDocumentSnapshot)) } + get() = ios.documents.map { DocumentSnapshot(NativeDocumentSnapshotWrapper(it as FIRDocumentSnapshot)) } actual val documentChanges get() = ios.documentChanges.map { DocumentChange(it as FIRDocumentChange) } actual val metadata: SnapshotMetadata get() = SnapshotMetadata(ios.metadata) @@ -390,7 +389,7 @@ actual class QuerySnapshot(val ios: FIRQuerySnapshot) { actual class DocumentChange(val ios: FIRDocumentChange) { actual val document: DocumentSnapshot - get() = DocumentSnapshot(NativeDocumentSnapshot(ios.document)) + get() = DocumentSnapshot(NativeDocumentSnapshotWrapper(ios.document)) actual val newIndex: Int get() = ios.newIndex.toInt() actual val oldIndex: Int @@ -399,27 +398,34 @@ actual class DocumentChange(val ios: FIRDocumentChange) { get() = ChangeType.values().first { it.ios == ios.type } } +actual typealias NativeDocumentSnapshot = FIRDocumentSnapshot + @PublishedApi -internal actual class NativeDocumentSnapshot(val ios: FIRDocumentSnapshot) { +internal actual class NativeDocumentSnapshotWrapper actual constructor(actual val native: NativeDocumentSnapshot) { - actual val id get() = ios.documentID + actual val id get() = native.documentID - actual val reference get() = NativeDocumentReference(ios.reference) + actual val reference get() = NativeDocumentReference(native.reference) actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = - ios.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + native.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + + // Despite its name implying otherwise, valueForField accepts both a String representation of a Field and a FIRFieldPath + actual fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.valueForField(fieldPath.ios, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = - ios.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) + native.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) ?.mapValues { (_, value) -> value?.takeIf { it !is NSNull } } - actual fun contains(field: String) = ios.valueForField(field) != null + actual fun contains(field: String) = native.valueForField(field) != null + actual fun contains(fieldPath: FieldPath) = native.valueForField(fieldPath.ios) != null - actual val exists get() = ios.exists + actual val exists get() = native.exists - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(ios.metadata) + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) fun ServerTimestampBehavior.toIos() : FIRServerTimestampBehavior = when (this) { ServerTimestampBehavior.ESTIMATE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorEstimate @@ -428,7 +434,7 @@ internal actual class NativeDocumentSnapshot(val ios: FIRDocumentSnapshot) { } } -val DocumentSnapshot.ios get() = native.ios +val DocumentSnapshot.ios get() = nativeWrapper.native actual class SnapshotMetadata(val ios: FIRSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = ios.pendingWrites diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 0a381c8f4..b2dfdb0e3 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -4,6 +4,7 @@ package dev.gitlive.firebase.firestore +import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException @@ -35,7 +36,6 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.promise -import kotlinx.serialization.Serializable import kotlin.js.Json import kotlin.js.json import dev.gitlive.firebase.firestore.externals.CollectionReference as JsCollectionReference @@ -72,19 +72,19 @@ actual class FirebaseFirestore(jsFirestore: Firestore) { var js: Firestore = jsFirestore private set - actual fun collection(collectionPath: String) = rethrow { CollectionReference(NativeCollectionReference(jsCollection(js, collectionPath))) } + actual fun collection(collectionPath: String) = rethrow { CollectionReference(NativeCollectionReferenceWrapper(jsCollection(js, collectionPath))) } actual fun collectionGroup(collectionId: String) = rethrow { Query(jsCollectionGroup(js, collectionId)) } actual fun document(documentPath: String) = rethrow { DocumentReference(NativeDocumentReference(doc(js, documentPath))) } - actual fun batch() = rethrow { WriteBatch(NativeWriteBatch(writeBatch(js))) } + actual fun batch() = rethrow { WriteBatch(NativeWriteBatchWrapper(writeBatch(js))) } actual fun setLoggingEnabled(loggingEnabled: Boolean) = rethrow { setLogLevel( if(loggingEnabled) "error" else "silent") } actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - rethrow { jsRunTransaction(js, { GlobalScope.promise { Transaction(NativeTransaction(it)).func() } } ).await() } + rethrow { jsRunTransaction(js, { GlobalScope.promise { Transaction(NativeTransactionWrapper(it)).func() } } ).await() } actual suspend fun clearPersistence() = rethrow { clearIndexedDbPersistence(js).await() } @@ -119,21 +119,21 @@ internal val SetOptions.js: Json get() = when (this) { } @PublishedApi -internal actual class NativeWriteBatch(val js: JsWriteBatch) { +internal actual class NativeWriteBatchWrapper(val js: JsWriteBatch) { actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): NativeWriteBatch = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } + ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatch = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData) } .let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): NativeWriteBatch = rethrow { + ): NativeWriteBatchWrapper = rethrow { encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } @@ -142,7 +142,7 @@ internal actual class NativeWriteBatch(val js: JsWriteBatch) { actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): NativeWriteBatch = rethrow { + ): NativeWriteBatchWrapper = rethrow { encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } @@ -158,24 +158,24 @@ internal actual class NativeWriteBatch(val js: JsWriteBatch) { val WriteBatch.js get() = native.js @PublishedApi -internal actual class NativeTransaction(val js: JsTransaction) { +internal actual class NativeTransactionWrapper(val js: JsTransaction) { actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): NativeTransaction = rethrow { + ): NativeTransactionWrapper = rethrow { js.set(documentRef.js, encodedData, setOptions.js) } .let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransaction = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData) } .let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): NativeTransaction = rethrow { + ): NativeTransactionWrapper = rethrow { encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } @@ -184,7 +184,7 @@ internal actual class NativeTransaction(val js: JsTransaction) { actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): NativeTransaction = rethrow { + ): NativeTransactionWrapper = rethrow { encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } @@ -195,10 +195,10 @@ internal actual class NativeTransaction(val js: JsTransaction) { .let { this } actual suspend fun get(documentRef: DocumentReference) = - rethrow { NativeDocumentSnapshot(js.get(documentRef.js).await()) } + rethrow { NativeDocumentSnapshotWrapper(js.get(documentRef.js).await()) } } -val Transaction.js get() = native.js +val Transaction.js get() = nativeWrapper.js /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = JsDocumentReference @@ -213,30 +213,30 @@ internal actual class NativeDocumentReference actual constructor(actual val nati actual val path: String get() = rethrow { js.path } - actual val parent: NativeCollectionReference - get() = rethrow { NativeCollectionReference(js.parent) } + actual val parent: NativeCollectionReferenceWrapper + get() = rethrow { NativeCollectionReferenceWrapper(js.parent) } - actual fun collection(collectionPath: String) = rethrow { NativeCollectionReference(jsCollection(js, collectionPath)) } + actual fun collection(collectionPath: String) = rethrow { NativeCollectionReferenceWrapper(jsCollection(js, collectionPath)) } - actual suspend fun get() = rethrow { NativeDocumentSnapshot( getDoc(js).await()) } + actual suspend fun get() = rethrow { NativeDocumentSnapshotWrapper( getDoc(js).await()) } - actual val snapshots: Flow get() = snapshots() + actual val snapshots: Flow get() = snapshots() - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { val unsubscribe = onSnapshot( js, json("includeMetadataChanges" to includeMetadataChanges), - { trySend(NativeDocumentSnapshot(it)) }, + { trySend(NativeDocumentSnapshotWrapper(it)) }, { close(errorToException(it)) } ) awaitClose { unsubscribe() } } - actual suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) = rethrow { + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { setDoc(js, encodedData, setOptions.js).await() } - actual suspend fun updateEncoded(encodedData: Any) = rethrow { jsUpdate(js, encodedData).await() } + actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData).await() } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { rethrow { @@ -268,13 +268,13 @@ internal actual class NativeDocumentReference actual constructor(actual val nati val DocumentReference.js get() = native.js @PublishedApi -internal actual open class NativeQuery(open val js: JsQuery) +internal actual open class NativeQueryWrapper(open val js: JsQuery) -actual open class Query internal actual constructor(nativeQuery: NativeQuery) { +actual open class Query internal actual constructor(nativeWrapper: NativeQueryWrapper) { - constructor(js: JsQuery) : this(NativeQuery(js)) + constructor(js: JsQuery) : this(NativeQueryWrapper(js)) - open val js: JsQuery = nativeQuery.js + open val js: JsQuery = nativeWrapper.js actual suspend fun get() = rethrow { QuerySnapshot(getDocs(js).await()) } @@ -367,7 +367,7 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } @PublishedApi -internal actual class NativeCollectionReference(override val js: JsCollectionReference) : NativeQuery(js) { +internal actual class NativeCollectionReferenceWrapper(override val js: JsCollectionReference) : NativeQueryWrapper(js) { actual val path: String get() = rethrow { js.path } @@ -383,7 +383,7 @@ internal actual class NativeCollectionReference(override val js: JsCollectionRef } } -val CollectionReference.js get() = native.js +val CollectionReference.js get() = nativeWrapper.js actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) @@ -392,7 +392,7 @@ actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code actual class QuerySnapshot(val js: JsQuerySnapshot) { actual val documents - get() = js.docs.map { DocumentSnapshot(NativeDocumentSnapshot(it)) } + get() = js.docs.map { DocumentSnapshot(NativeDocumentSnapshotWrapper(it)) } actual val documentChanges get() = js.docChanges().map { DocumentChange(it) } actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) @@ -400,7 +400,7 @@ actual class QuerySnapshot(val js: JsQuerySnapshot) { actual class DocumentChange(val js: JsDocumentChange) { actual val document: DocumentSnapshot - get() = DocumentSnapshot(NativeDocumentSnapshot(js.doc)) + get() = DocumentSnapshot(NativeDocumentSnapshotWrapper(js.doc)) actual val newIndex: Int get() = js.newIndex actual val oldIndex: Int @@ -410,7 +410,7 @@ actual class DocumentChange(val js: JsDocumentChange) { } @PublishedApi -internal actual class NativeDocumentSnapshot(val js: JsDocumentSnapshot) { +internal actual class NativeDocumentSnapshotWrapper(val js: JsDocumentSnapshot) { actual val id get() = rethrow { js.id } actual val reference get() = rethrow { NativeDocumentReference(js.ref) } @@ -431,7 +431,7 @@ internal actual class NativeDocumentSnapshot(val js: JsDocumentSnapshot) { json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) } -val DocumentSnapshot.js get() = native.js +val DocumentSnapshot.js get() = nativeWrapper.js actual class SnapshotMetadata(val js: JsSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = js.hasPendingWrites From dc547148170d037b4bd93b3d352c7596a4f809e9 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 12 Apr 2024 01:03:22 +0200 Subject: [PATCH 04/12] More stable implementation of Firestore encoding Make methods stricter and fail with Library exceptions so they can be caught consistently. --- .../kotlin/dev/gitlive/firebase/_encoders.kt | 22 ++- .../kotlin/dev/gitlive/firebase/encoders.kt | 38 +++-- .../kotlin/dev/gitlive/firebase/_encoders.kt | 20 +-- .../kotlin/dev/gitlive/firebase/_encoders.kt | 31 ++-- .../dev/gitlive/firebase/database/database.kt | 4 +- .../gitlive/firebase/firestore/firestore.kt | 132 +++++++++--------- .../gitlive/firebase/firestore/firestore.kt | 106 ++++++++------ .../gitlive/firebase/firestore/firestore.kt | 79 +++++------ .../gitlive/firebase/firestore/firestore.kt | 108 ++++++++------ 9 files changed, 302 insertions(+), 238 deletions(-) diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt index bcfec9152..a13e9a859 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,22 +10,18 @@ import kotlinx.serialization.descriptors.StructureKind import java.lang.IllegalArgumentException import kotlin.collections.set -actual data class EncodedObject( - val android: Map -) +actual data class EncodedObject(actual val raw: Map) { + actual companion object { + actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) + } + val android: Map get() = raw +} -actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) +@PublishedApi +internal actual fun List>.asEncodedObject() = EncodedObject(toMap()) @PublishedApi -internal actual fun Map<*, *>.asEncodedObject() = EncodedObject( - map { (key, value) -> - if (key is String) { - key to value - } else { - throw IllegalArgumentException("Expected a String key but received $key") - } - }.toMap() -) +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> mutableListOf() diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 363ecbaa6..a74f2d24f 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -11,9 +11,13 @@ import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule -expect class EncodedObject +expect class EncodedObject { + companion object { + val emptyEncodedObject: EncodedObject + } -expect val emptyEncodedObject: EncodedObject + val raw: Map +} @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(strategy, value) { encodeDefaults = shouldEncodeElementDefault }")) fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = encode(strategy, value) { @@ -36,20 +40,12 @@ inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) inline fun encodeAsObject(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { - val encoded = encode(strategy, value, buildSettings) - return when (encoded) { - is Map<*, *> -> encoded.asEncodedObject() - null -> throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") - else -> throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") - } + val encoded = encode(strategy, value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") } inline fun encodeAsObject(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { - val encoded = encode(value, buildSettings) - return when (encoded) { - is Map<*, *> -> encoded.asEncodedObject() - null -> throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") - else -> throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") - } + val encoded = encode(value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") } @PublishedApi @@ -67,7 +63,19 @@ internal inline fun encode(value: T, encodeSettings: EncodeSettings) } @PublishedApi -internal expect fun Map<*, *>.asEncodedObject(): EncodedObject +expect internal fun Any.asNativeMap(): Map<*, *>? + +@PublishedApi +internal fun Map<*, *>.asEncodedObject(): EncodedObject = map { (key, value) -> + if (key is String) { + key to value + } else { + throw IllegalArgumentException("Expected a String key but received $key") + } +}.asEncodedObject() + +@PublishedApi +internal expect fun List>.asEncodedObject(): EncodedObject /** * An extension which which serializer to use for value. Handy in updating fields by name or path diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt index 58cecc4d9..da99abbdc 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -9,18 +9,20 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual data class EncodedObject( - val ios: Map -) +actual data class EncodedObject(actual val raw: Map) { + actual companion object { + actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) + } -actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) + val ios: Map get() = raw.mapKeys { (key, _) -> key as? Any } +} @PublishedApi -internal actual fun Map<*, *>.asEncodedObject() = EncodedObject( - map { (key, value) -> - key to value - }.toMap() -) +internal actual fun List>.asEncodedObject() = EncodedObject(toMap()) + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> + actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> encodeAsList() StructureKind.MAP -> mutableListOf() diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index 547f8b5af..00cafbce8 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,19 +10,28 @@ import kotlinx.serialization.descriptors.StructureKind import kotlin.js.Json import kotlin.js.json -actual typealias EncodedObject = Json +actual data class EncodedObject(private val keyValues: List>) { + actual companion object { + actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyList()) + } + + actual val raw get() = keyValues.toMap() + val json get() = json(*keyValues.toTypedArray()) +} + -actual val emptyEncodedObject: EncodedObject = json() @PublishedApi -internal actual fun Map<*, *>.asEncodedObject() = json( - *map { (key, value) -> - if (key is String) { - key to value - } else { - throw IllegalArgumentException("Expected a String key but received $key") - } - }.toTypedArray() -) +internal actual fun List>.asEncodedObject() = EncodedObject(this) + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = (this as? Json)?.let { json -> + val mutableMap = mutableMapOf() + for (key in js("Object").keys(json)) { + mutableMap[key] = json[key] + } + mutableMap.toMap() +} + actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> encodeAsList(descriptor) StructureKind.MAP -> { diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index c67edf051..4e48d612c 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -184,7 +184,7 @@ internal actual class NativeDatabaseReference internal constructor( } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { update(js, encodedUpdate).awaitWhileOnline(database) } + rethrow { update(js, encodedUpdate.json).awaitWhileOnline(database) } actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { @@ -236,7 +236,7 @@ internal actual class NativeOnDisconnect internal constructor( rethrow { js.set(encodedValue).awaitWhileOnline(database) } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { js.update(encodedUpdate).awaitWhileOnline(database) } + rethrow { js.update(encodedUpdate.json).awaitWhileOnline(database) } } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 1e7a557f8..c82d12440 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -28,7 +28,7 @@ actual class FirebaseFirestore(val android: com.google.firebase.firestore.Fireba actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReferenceWrapper(android.collection(collectionPath))) - actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId).native) + actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId).wrapped) actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(android.document(documentPath))) @@ -74,84 +74,87 @@ internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) } +actual typealias NativeWriteBatch = com.google.firebase.firestore.WriteBatch + @PublishedApi -internal actual class NativeWriteBatchWrapper(val android: com.google.firebase.firestore.WriteBatch) { +internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions ): NativeWriteBatchWrapper = (setOptions.android?.let { - android.set(documentRef.android, encodedData, it) - } ?: android.set(documentRef.android, encodedData)).let { + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android)).let { this } - @Suppress("UNCHECKED_CAST") - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any) = android.update(documentRef.android, encodedData as Map).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) + native.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) + native.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } + native.delete(documentRef.android).let { this } actual suspend fun commit() { - android.commit().await() + native.commit().await() } } -val WriteBatch.android get() = native.android +val WriteBatch.android get() = native + +actual typealias NativeTransaction = com.google.firebase.firestore.Transaction @PublishedApi -internal actual class NativeTransactionWrapper(val android: com.google.firebase.firestore.Transaction) { +internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions ): NativeTransactionWrapper { setOptions.android?.let { - android.set(documentRef.android, encodedData, it) - } ?: android.set(documentRef.android, encodedData) + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android) return this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = android.update(documentRef.android, encodedData.android).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) + native.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) + native.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } + native.delete(documentRef.android).let { this } actual suspend fun get(documentRef: DocumentReference) = - NativeDocumentSnapshotWrapper(android.get(documentRef.android)) + NativeDocumentSnapshotWrapper(native.get(documentRef.android)) } -val Transaction.android get() = nativeWrapper.android +val Transaction.android get() = native /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = com.google.firebase.firestore.DocumentReference @@ -220,20 +223,16 @@ internal actual class NativeDocumentReference actual constructor(actual val nati val DocumentReference.android get() = native.android -@PublishedApi -internal actual open class NativeQueryWrapper(open val android: AndroidQuery) -internal val AndroidQuery.native get() = NativeQueryWrapper(this) - -actual open class Query internal actual constructor(nativeWrapper: NativeQueryWrapper) { +actual typealias NativeQuery = AndroidQuery - open val android = nativeWrapper.android - - actual suspend fun get() = QuerySnapshot(android.get().await()) +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: AndroidQuery) { + actual suspend fun get() = QuerySnapshot(native.get().await()) - actual fun limit(limit: Number) = Query(NativeQueryWrapper(android.limit(limit.toLong()))) + actual fun limit(limit: Number) = native.limit(limit.toLong()).wrapped actual val snapshots get() = callbackFlow { - val listener = android.addSnapshotListener { snapshot, exception -> + val listener = native.addSnapshotListener { snapshot, exception -> snapshot?.let { trySend(QuerySnapshot(snapshot)) } exception?.let { close(exception) } } @@ -242,16 +241,14 @@ actual open class Query internal actual constructor(nativeWrapper: NativeQueryWr actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> + val listener = native.addSnapshotListener(metadataChanges) { snapshot, exception -> snapshot?.let { trySend(QuerySnapshot(snapshot)) } exception?.let { close(exception) } } awaitClose { listener.remove() } } - internal actual fun where(filter: Filter) = Query( - android.where(filter.toAndroidFilter()).native - ) + actual fun where(filter: Filter) = native.where(filter.toAndroidFilter()).wrapped private fun Filter.toAndroidFilter(): AndroidFilter = when (this) { is Filter.And -> AndroidFilter.and(*filters.map { it.toAndroidFilter() }.toTypedArray()) @@ -316,41 +313,47 @@ actual open class Query internal actual constructor(nativeWrapper: NativeQueryWr } } - internal actual fun _orderBy(field: String, direction: Direction) = Query(android.orderBy(field, direction).native) - internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(android.orderBy(field.android, direction).native) + actual fun orderBy(field: String, direction: Direction) = native.orderBy(field, direction).wrapped + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.orderBy(field, direction).wrapped - internal actual fun _startAfter(document: DocumentSnapshot) = Query(android.startAfter(document.android).native) - internal actual fun _startAfter(vararg fieldValues: Any) = Query(android.startAfter(*fieldValues).native) - internal actual fun _startAt(document: DocumentSnapshot) = Query(android.startAt(document.android).native) - internal actual fun _startAt(vararg fieldValues: Any) = Query(android.startAt(*fieldValues).native) + actual fun startAfter(document: NativeDocumentSnapshot) = native.startAfter(document).wrapped + actual fun startAfter(vararg fieldValues: Any) = native.startAfter(*fieldValues).wrapped + actual fun startAt(document: NativeDocumentSnapshot) = native.startAt(document).wrapped + actual fun startAt(vararg fieldValues: Any) = native.startAt(*fieldValues).wrapped - internal actual fun _endBefore(document: DocumentSnapshot) = Query(android.endBefore(document.android).native) - internal actual fun _endBefore(vararg fieldValues: Any) = Query(android.endBefore(*fieldValues).native) - internal actual fun _endAt(document: DocumentSnapshot) = Query(android.endAt(document.android).native) - internal actual fun _endAt(vararg fieldValues: Any) = Query(android.endAt(*fieldValues).native) + actual fun endBefore(document: NativeDocumentSnapshot) = native.endBefore(document).wrapped + actual fun endBefore(vararg fieldValues: Any) = native.endBefore(*fieldValues).wrapped + actual fun endAt(document: NativeDocumentSnapshot) = native.endAt(document).wrapped + actual fun endAt(vararg fieldValues: Any) = native.endAt(*fieldValues).wrapped } +val Query.android get() = native + +internal val AndroidQuery.wrapped get() = NativeQueryWrapper(this) + actual typealias Direction = com.google.firebase.firestore.Query.Direction actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type +actual typealias NativeCollectionReference = com.google.firebase.firestore.CollectionReference + @PublishedApi -internal actual class NativeCollectionReferenceWrapper(override val android: com.google.firebase.firestore.CollectionReference) : NativeQueryWrapper(android) { +internal actual class NativeCollectionReferenceWrapper internal actual constructor(override actual val native: NativeCollectionReference) : NativeQueryWrapper(native) { actual val path: String - get() = android.path + get() = native.path actual val document: NativeDocumentReference - get() = NativeDocumentReference(android.document()) + get() = NativeDocumentReference(native.document()) actual val parent: NativeDocumentReference? - get() = android.parent?.let{ NativeDocumentReference(it) } + get() = native.parent?.let{ NativeDocumentReference(it) } - actual fun document(documentPath: String) = NativeDocumentReference(android.document(documentPath)) + actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - actual suspend fun addEncoded(data: Any) = NativeDocumentReference(android.add(data).await()) + actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(native.add(data.android).await()) } -val CollectionReference.android get() = nativeWrapper.android +val CollectionReference.android get() = native actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException @@ -377,21 +380,24 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC get() = android.type } +actual typealias NativeDocumentSnapshot = com.google.firebase.firestore.DocumentSnapshot + @PublishedApi -internal actual class NativeDocumentSnapshotWrapper(val android: com.google.firebase.firestore.DocumentSnapshot) { +internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: com.google.firebase.firestore.DocumentSnapshot) { - actual val id get() = android.id - actual val reference get() = NativeDocumentReference(android.reference) + actual val id get() = native.id + actual val reference get() = NativeDocumentReference(native.reference) - actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = android.get(field, serverTimestampBehavior.toAndroid()) - actual fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = android.get(fieldPath.encoded, serverTimestampBehavior.toAndroid()) - actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = android.getData(serverTimestampBehavior.toAndroid()) + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(field, serverTimestampBehavior.toAndroid()) + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(fieldPath, serverTimestampBehavior.toAndroid()) + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = native.getData(serverTimestampBehavior.toAndroid()) - actual fun contains(field: String) = android.contains(field) + actual fun contains(field: String) = native.contains(field) + actual fun contains(fieldPath: EncodedFieldPath) = native.contains(fieldPath) - actual val exists get() = android.exists() + actual val exists get() = native.exists() - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.metadata) + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) fun ServerTimestampBehavior.toAndroid(): com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior = when (this) { ServerTimestampBehavior.ESTIMATE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.ESTIMATE @@ -400,7 +406,7 @@ internal actual class NativeDocumentSnapshotWrapper(val android: com.google.fire } } -val DocumentSnapshot.android get() = nativeWrapper.android +val DocumentSnapshot.android get() = native actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { actual val hasPendingWrites: Boolean get() = android.hasPendingWrites() diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 88915b86e..0aaf75146 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -132,32 +132,60 @@ data class Transaction internal constructor(@PublishedApi internal val nativeWra suspend fun get(documentRef: DocumentReference): DocumentSnapshot = DocumentSnapshot(nativeWrapper.get(documentRef)) } +expect open class NativeQuery + @PublishedApi -internal expect open class NativeQuery +internal expect open class NativeQueryWrapper internal constructor(native: NativeQuery) { + + open val native: NativeQuery -expect open class Query internal constructor(nativeQuery: NativeQuery) { - fun limit(limit: Number): Query + fun limit(limit: Number): NativeQueryWrapper val snapshots: Flow fun snapshots(includeMetadataChanges: Boolean = false): Flow suspend fun get(): QuerySnapshot - internal fun where(filter: Filter): Query + fun where(filter: Filter): NativeQueryWrapper - internal fun _orderBy(field: String, direction: Direction): Query - internal fun _orderBy(field: FieldPath, direction: Direction): Query + fun orderBy(field: String, direction: Direction): NativeQueryWrapper + fun orderBy(field: EncodedFieldPath, direction: Direction): NativeQueryWrapper - internal fun _startAfter(document: DocumentSnapshot): Query - internal fun _startAfter(vararg fieldValues: Any): Query - internal fun _startAt(document: DocumentSnapshot): Query - internal fun _startAt(vararg fieldValues: Any): Query + fun startAfter(document: NativeDocumentSnapshot): NativeQueryWrapper + fun startAfter(vararg fieldValues: Any): NativeQueryWrapper + fun startAt(document: NativeDocumentSnapshot): NativeQueryWrapper + fun startAt(vararg fieldValues: Any): NativeQueryWrapper - internal fun _endBefore(document: DocumentSnapshot): Query - internal fun _endBefore(vararg fieldValues: Any): Query - internal fun _endAt(document: DocumentSnapshot): Query - internal fun _endAt(vararg fieldValues: Any): Query + fun endBefore(document: NativeDocumentSnapshot): NativeQueryWrapper + fun endBefore(vararg fieldValues: Any): NativeQueryWrapper + fun endAt(document: NativeDocumentSnapshot): NativeQueryWrapper + fun endAt(vararg fieldValues: Any): NativeQueryWrapper } -fun Query.where(builder: FilterBuilder.() -> Filter?) = builder(FilterBuilder())?.let { where(it) } ?: this +open class Query internal constructor(internal val nativeQuery: NativeQueryWrapper) { + + constructor(native: NativeQuery) : this(NativeQueryWrapper(native)) + + open val native = nativeQuery.native + + fun limit(limit: Number): Query = Query(nativeQuery.limit(limit)) + val snapshots: Flow = nativeQuery.snapshots + fun snapshots(includeMetadataChanges: Boolean = false): Flow = nativeQuery.snapshots(includeMetadataChanges) + suspend fun get(): QuerySnapshot = nativeQuery.get() + + fun where(builder: FilterBuilder.() -> Filter?) = builder(FilterBuilder())?.let { Query(nativeQuery.where(it)) } ?: this + + fun orderBy(field: String, direction: Direction = Direction.ASCENDING) = Query(nativeQuery.orderBy(field, direction)) + fun orderBy(field: FieldPath, direction: Direction = Direction.ASCENDING) = Query(nativeQuery.orderBy(field.encoded, direction)) + + fun startAfter(document: DocumentSnapshot) = Query(nativeQuery.startAfter(document.native)) + fun startAfter(vararg fieldValues: Any) = Query(nativeQuery.startAfter(*(fieldValues.map { it.safeValue }.toTypedArray()))) + fun startAt(document: DocumentSnapshot) = Query(nativeQuery.startAt(document.native)) + fun startAt(vararg fieldValues: Any) = Query(nativeQuery.startAt(*(fieldValues.map { it.safeValue }.toTypedArray()))) + + fun endBefore(document: DocumentSnapshot) = Query(nativeQuery.endBefore(document.native)) + fun endBefore(vararg fieldValues: Any) = Query(nativeQuery.endBefore(*(fieldValues.map { it.safeValue }.toTypedArray()))) + fun endAt(document: DocumentSnapshot) = Query(nativeQuery.endAt(document.native)) + fun endAt(vararg fieldValues: Any) = Query(nativeQuery.endAt(*(fieldValues.map { it.safeValue }.toTypedArray()))) +} @Deprecated("Deprecated in favor of using a [FilterBuilder]", replaceWith = ReplaceWith("where { field equalTo equalTo }", "dev.gitlive.firebase.firestore")) fun Query.where(field: String, equalTo: Any?) = where { @@ -211,19 +239,6 @@ fun Query.where(path: FieldPath, inArray: List? = null, arrayContainsAny: L ) } -fun Query.orderBy(field: String, direction: Direction = Direction.ASCENDING) = _orderBy(field, direction) -fun Query.orderBy(field: FieldPath, direction: Direction = Direction.ASCENDING) = _orderBy(field, direction) - -fun Query.startAfter(document: DocumentSnapshot) = _startAfter(document) -fun Query.startAfter(vararg fieldValues: Any) = _startAfter(*(fieldValues.mapNotNull { it.safeValue }.toTypedArray())) -fun Query.startAt(document: DocumentSnapshot) = _startAt(document) -fun Query.startAt(vararg fieldValues: Any) = _startAt(*(fieldValues.mapNotNull { it.safeValue }.toTypedArray())) - -fun Query.endBefore(document: DocumentSnapshot) = _endBefore(document) -fun Query.endBefore(vararg fieldValues: Any) = _endBefore(*(fieldValues.mapNotNull { it.safeValue }.toTypedArray())) -fun Query.endAt(document: DocumentSnapshot) = _endAt(document) -fun Query.endAt(vararg fieldValues: Any) = _endAt(*(fieldValues.mapNotNull { it.safeValue }.toTypedArray())) - internal val Any.safeValue: Any get() = when (this) { is Timestamp -> nativeValue is GeoPoint -> nativeValue @@ -236,7 +251,8 @@ internal val Any.safeValue: Any get() = when (this) { expect class NativeWriteBatch @PublishedApi -internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch){ +internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch) { + val native: NativeWriteBatch fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeWriteBatchWrapper fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper @@ -245,10 +261,12 @@ internal expect class NativeWriteBatchWrapper internal constructor(native: Nativ suspend fun commit() } -data class WriteBatch internal constructor(@PublishedApi internal val native: NativeWriteBatchWrapper) { +data class WriteBatch internal constructor(@PublishedApi internal val nativeWrapper: NativeWriteBatchWrapper) { constructor(native: NativeWriteBatch) : this(NativeWriteBatchWrapper(native)) + val native = nativeWrapper.native + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults }")) inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults @@ -292,7 +310,7 @@ data class WriteBatch internal constructor(@PublishedApi internal val native: Na setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @PublishedApi - internal fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions) = WriteBatch(native.setEncoded(documentRef, encodedData, setOptions)) + internal fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions) = WriteBatch(nativeWrapper.setEncoded(documentRef, encodedData, setOptions)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { this.encodeDefaults = encodeDefaults }")) inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = update(documentRef, data) { @@ -314,16 +332,16 @@ data class WriteBatch internal constructor(@PublishedApi internal val native: Na inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @PublishedApi - internal fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = WriteBatch(native.updateEncoded(documentRef, encodedData)) + internal fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = WriteBatch(nativeWrapper.updateEncoded(documentRef, encodedData)) @PublishedApi - internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(native.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) + internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(nativeWrapper.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) @PublishedApi - internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(native.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) + internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(nativeWrapper.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) - fun delete(documentRef: DocumentReference): WriteBatch = WriteBatch(native.delete(documentRef)) - suspend fun commit() = native.commit() + fun delete(documentRef: DocumentReference): WriteBatch = WriteBatch(nativeWrapper.delete(documentRef)) + suspend fun commit() = nativeWrapper.commit() } /** A class representing a platform specific Firebase DocumentReference. */ @@ -427,12 +445,12 @@ data class DocumentReference internal constructor(@PublishedApi internal val nat suspend fun delete() = native.delete() } -expect class NativeCollectionReference +expect class NativeCollectionReference : NativeQuery @PublishedApi -internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : NativeQuery { +internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : NativeQueryWrapper { - val native: NativeCollectionReference + override val native: NativeCollectionReference val path: String val document: NativeDocumentReference @@ -446,7 +464,7 @@ data class CollectionReference internal constructor(@PublishedApi internal val n constructor(native: NativeCollectionReference) : this(NativeCollectionReferenceWrapper(native)) - val native = nativeWrapper.native + override val native = nativeWrapper.native val path: String get() = nativeWrapper.path val document: DocumentReference get() = DocumentReference(nativeWrapper.document) @@ -535,10 +553,10 @@ internal expect class NativeDocumentSnapshotWrapper internal constructor(native: val metadata: SnapshotMetadata fun contains(field: String): Boolean - fun contains(fieldPath: FieldPath): Boolean + fun contains(fieldPath: EncodedFieldPath): Boolean fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? } @@ -554,7 +572,7 @@ data class DocumentSnapshot internal constructor(@PublishedApi internal val nati val metadata: SnapshotMetadata get() = nativeWrapper.metadata fun contains(field: String): Boolean = nativeWrapper.contains(field) - fun contains(fieldPath: FieldPath): Boolean = nativeWrapper.contains(fieldPath) + fun contains(fieldPath: FieldPath): Boolean = nativeWrapper.contains(fieldPath.encoded) inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(value = getEncoded(field, serverTimestampBehavior), buildSettings) inline fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(field, serverTimestampBehavior), buildSettings) @@ -566,7 +584,7 @@ data class DocumentSnapshot internal constructor(@PublishedApi internal val nati inline fun get(fieldPath: FieldPath, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(fieldPath, serverTimestampBehavior), buildSettings) @PublishedApi - internal fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.getEncoded(fieldPath, serverTimestampBehavior) + internal fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.getEncoded(fieldPath.encoded, serverTimestampBehavior) inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(encodedData(serverTimestampBehavior), buildSettings) diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 2689c0950..ec87011db 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -26,7 +26,7 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReferenceWrapper(ios.collectionWithPath(collectionPath))) - actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId).native) + actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId).wrapped) actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(ios.documentWithPath(documentPath))) @@ -70,7 +70,7 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { actual typealias NativeWriteBatch = FIRWriteBatch @PublishedApi -internal actual class NativeWriteBatchWrapper actual constructor(val native: FIRWriteBatch) { +internal actual class NativeWriteBatchWrapper actual constructor(actual val native: NativeWriteBatch) { actual fun setEncoded( documentRef: DocumentReference, @@ -107,7 +107,7 @@ internal actual class NativeWriteBatchWrapper actual constructor(val native: FIR actual suspend fun commit() = await { native.commitWithCompletion(it) } } -val WriteBatch.ios get() = native.native +val WriteBatch.ios get() = native actual typealias NativeTransaction = FIRTransaction @@ -151,7 +151,7 @@ internal actual class NativeTransactionWrapper actual constructor(actual val nat } -val Transaction.ios get() = nativeWrapper.native +val Transaction.ios get() = native /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = FIRDocumentReference @@ -223,20 +223,16 @@ internal actual class NativeDocumentReference actual constructor(actual val nati val DocumentReference.ios get() = native.ios -@PublishedApi -internal actual open class NativeQuery(open val ios: FIRQuery) -internal val FIRQuery.native get() = NativeQuery(this) - -actual open class Query internal actual constructor(nativeQuery: NativeQuery) { - - open val ios: FIRQuery = nativeQuery.ios +actual typealias NativeQuery = FIRQuery - actual suspend fun get() = QuerySnapshot(awaitResult { ios.getDocumentsWithCompletion(it) }) +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { + actual suspend fun get() = QuerySnapshot(awaitResult { native.getDocumentsWithCompletion(it) }) - actual fun limit(limit: Number) = Query(ios.queryLimitedTo(limit.toLong()).native) + actual fun limit(limit: Number) = native.queryLimitedTo(limit.toLong()).wrapped actual val snapshots get() = callbackFlow { - val listener = ios.addSnapshotListener { snapshot, error -> + val listener = native.addSnapshotListener { snapshot, error -> snapshot?.let { trySend(QuerySnapshot(snapshot)) } error?.let { close(error.toException()) } } @@ -244,16 +240,14 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> + val listener = native.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> snapshot?.let { trySend(QuerySnapshot(snapshot)) } error?.let { close(error.toException()) } } awaitClose { listener.remove() } } - internal actual fun where(filter: Filter): Query = Query( - ios.queryWhereFilter(filter.toFIRFilter()).native - ) + actual fun where(filter: Filter) = native.queryWhereFilter(filter.toFIRFilter()).wrapped private fun Filter.toFIRFilter(): FIRFilter = when (this) { is Filter.And -> FIRFilter.andFilterWithFilters(filters.map { it.toFIRFilter() }) @@ -284,41 +278,42 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } } - internal actual fun _orderBy(field: String, direction: Direction) = Query(ios.queryOrderedByField(field, direction == Direction.DESCENDING).native) - internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(ios.queryOrderedByFieldPath(field.ios, direction == Direction.DESCENDING).native) + actual fun orderBy(field: String, direction: Direction) = native.queryOrderedByField(field, direction == Direction.DESCENDING).wrapped + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.queryOrderedByFieldPath(field, direction == Direction.DESCENDING).wrapped - internal actual fun _startAfter(document: DocumentSnapshot) = Query(ios.queryStartingAfterDocument(document.ios).native) - internal actual fun _startAfter(vararg fieldValues: Any) = Query(ios.queryStartingAfterValues(fieldValues.asList()).native) - internal actual fun _startAt(document: DocumentSnapshot) = Query(ios.queryStartingAtDocument(document.ios).native) - internal actual fun _startAt(vararg fieldValues: Any) = Query(ios.queryStartingAtValues(fieldValues.asList()).native) - - internal actual fun _endBefore(document: DocumentSnapshot) = Query(ios.queryEndingBeforeDocument(document.ios).native) - internal actual fun _endBefore(vararg fieldValues: Any) = Query(ios.queryEndingBeforeValues(fieldValues.asList()).native) - internal actual fun _endAt(document: DocumentSnapshot) = Query(ios.queryEndingAtDocument(document.ios).native) - internal actual fun _endAt(vararg fieldValues: Any) = Query(ios.queryEndingAtValues(fieldValues.asList()).native) + actual fun startAfter(document: NativeDocumentSnapshot) = native.queryStartingAfterDocument(document).wrapped + actual fun startAfter(vararg fieldValues: Any) = native.queryStartingAfterValues(fieldValues.asList()).wrapped + actual fun startAt(document: NativeDocumentSnapshot) = native.queryStartingAtDocument(document).wrapped + actual fun startAt(vararg fieldValues: Any) = native.queryStartingAtValues(fieldValues.asList()).wrapped + actual fun endBefore(document: NativeDocumentSnapshot) = native.queryEndingBeforeDocument(document).wrapped + actual fun endBefore(vararg fieldValues: Any) = native.queryEndingBeforeValues(fieldValues.asList()).wrapped + actual fun endAt(document: NativeDocumentSnapshot) = native.queryEndingAtDocument(document).wrapped + actual fun endAt(vararg fieldValues: Any) = native.queryEndingAtValues(fieldValues.asList()).wrapped } +val Query.ios get() = native + +internal val FIRQuery.wrapped get() = NativeQueryWrapper(this) + actual typealias NativeCollectionReference = FIRCollectionReference @PublishedApi -internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual val native: NativeCollectionReference) : NativeQuery(native) { - - override val ios: FIRCollectionReference = native +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { actual val path: String - get() = ios.path + get() = native.path - actual val document get() = NativeDocumentReference(ios.documentWithAutoID()) + actual val document get() = NativeDocumentReference(native.documentWithAutoID()) - actual val parent get() = ios.parent?.let{ NativeDocumentReference(it) } + actual val parent get() = native.parent?.let{ NativeDocumentReference(it) } - actual fun document(documentPath: String) = NativeDocumentReference(ios.documentWithPath(documentPath)) + actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { ios.addDocumentWithData(data.ios, it) }) + actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { native.addDocumentWithData(data.ios, it) }) } -val CollectionReference.ios get() = nativeWrapper.ios +val CollectionReference.ios get() = native actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) @@ -411,8 +406,8 @@ internal actual class NativeDocumentSnapshotWrapper actual constructor(actual va native.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } // Despite its name implying otherwise, valueForField accepts both a String representation of a Field and a FIRFieldPath - actual fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = - native.valueForField(fieldPath.ios, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.valueForField(fieldPath, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = native.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) @@ -421,7 +416,7 @@ internal actual class NativeDocumentSnapshotWrapper actual constructor(actual va } actual fun contains(field: String) = native.valueForField(field) != null - actual fun contains(fieldPath: FieldPath) = native.valueForField(fieldPath.ios) != null + actual fun contains(fieldPath: EncodedFieldPath) = native.valueForField(fieldPath) != null actual val exists get() = native.exists @@ -434,7 +429,7 @@ internal actual class NativeDocumentSnapshotWrapper actual constructor(actual va } } -val DocumentSnapshot.ios get() = nativeWrapper.native +val DocumentSnapshot.ios get() = native actual class SnapshotMetadata(val ios: FIRSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = ios.pendingWrites diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index b2dfdb0e3..c6b3effff 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -74,7 +74,7 @@ actual class FirebaseFirestore(jsFirestore: Firestore) { actual fun collection(collectionPath: String) = rethrow { CollectionReference(NativeCollectionReferenceWrapper(jsCollection(js, collectionPath))) } - actual fun collectionGroup(collectionId: String) = rethrow { Query(jsCollectionGroup(js, collectionId)) } + actual fun collectionGroup(collectionId: String) = rethrow { Query(jsCollectionGroup(js, collectionId).wrapped) } actual fun document(documentPath: String) = rethrow { DocumentReference(NativeDocumentReference(doc(js, documentPath))) } @@ -118,16 +118,22 @@ internal val SetOptions.js: Json get() = when (this) { is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) } +actual data class NativeWriteBatch(val js: JsWriteBatch) + @PublishedApi -internal actual class NativeWriteBatchWrapper(val js: JsWriteBatch) { +internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { + + constructor(js: JsWriteBatch) : this(NativeWriteBatch(js)) + + val js = native.js actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions - ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } + ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.json, setOptions.js) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.json) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -157,19 +163,25 @@ internal actual class NativeWriteBatchWrapper(val js: JsWriteBatch) { val WriteBatch.js get() = native.js +actual data class NativeTransaction(val js: JsTransaction) + @PublishedApi -internal actual class NativeTransactionWrapper(val js: JsTransaction) { +internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { + + constructor(js: JsTransaction) : this(NativeTransaction(js)) + + val js = native.js actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions ): NativeTransactionWrapper = rethrow { - js.set(documentRef.js, encodedData, setOptions.js) + js.set(documentRef.js, encodedData.json, setOptions.js) } .let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.json) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -198,7 +210,7 @@ internal actual class NativeTransactionWrapper(val js: JsTransaction) { rethrow { NativeDocumentSnapshotWrapper(js.get(documentRef.js).await()) } } -val Transaction.js get() = nativeWrapper.js +val Transaction.js get() = native.js /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = JsDocumentReference @@ -233,10 +245,10 @@ internal actual class NativeDocumentReference actual constructor(actual val nati } actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { - setDoc(js, encodedData, setOptions.js).await() + setDoc(js, encodedData.json, setOptions.js).await() } - actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData).await() } + actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData.json).await() } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { rethrow { @@ -267,22 +279,21 @@ internal actual class NativeDocumentReference actual constructor(actual val nati val DocumentReference.js get() = native.js -@PublishedApi -internal actual open class NativeQueryWrapper(open val js: JsQuery) +actual open class NativeQuery(open val js: JsQuery) +internal val JsQuery.wrapped get() = NativeQueryWrapper(this) -actual open class Query internal actual constructor(nativeWrapper: NativeQueryWrapper) { +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { - constructor(js: JsQuery) : this(NativeQueryWrapper(js)) + constructor(js: JsQuery) : this(NativeQuery(js)) - open val js: JsQuery = nativeWrapper.js + open val js: JsQuery get() = native.js actual suspend fun get() = rethrow { QuerySnapshot(getDocs(js).await()) } - actual fun limit(limit: Number) = Query(query(js, jsLimit(limit))) + actual fun limit(limit: Number) = query(js, jsLimit(limit)).wrapped - internal actual fun where(filter: Filter): Query = Query( - query(js, filter.toQueryConstraint()) - ) + actual fun where(filter: Filter) = query(js, filter.toQueryConstraint()).wrapped private fun Filter.toQueryConstraint(): QueryConstraint = when (this) { is Filter.And -> and(*filters.map { it.toQueryConstraint() }.toTypedArray()) @@ -318,29 +329,29 @@ actual open class Query internal actual constructor(nativeWrapper: NativeQueryWr is WhereConstraint.NotInArray -> "not-in" } - internal actual fun _orderBy(field: String, direction: Direction) = rethrow { - Query(query(js, orderBy(field, direction.jsString))) + actual fun orderBy(field: String, direction: Direction) = rethrow { + query(js, orderBy(field, direction.jsString)).wrapped } - internal actual fun _orderBy(field: FieldPath, direction: Direction) = rethrow { - Query(query(js, orderBy(field.js, direction.jsString))) + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = rethrow { + query(js, orderBy(field, direction.jsString)).wrapped } - internal actual fun _startAfter(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAfter(document.js))) } + actual fun startAfter(document: NativeDocumentSnapshot) = rethrow { query(js, jsStartAfter(document.js)).wrapped } - internal actual fun _startAfter(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAfter(*fieldValues))) } + actual fun startAfter(vararg fieldValues: Any) = rethrow { query(js, jsStartAfter(*fieldValues)).wrapped } - internal actual fun _startAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAt(document.js))) } + actual fun startAt(document: NativeDocumentSnapshot) = rethrow { query(js, jsStartAt(document.js)).wrapped } - internal actual fun _startAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAt(*fieldValues))) } + actual fun startAt(vararg fieldValues: Any) = rethrow { query(js, jsStartAt(*fieldValues)).wrapped } - internal actual fun _endBefore(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndBefore(document.js))) } + actual fun endBefore(document: NativeDocumentSnapshot) = rethrow { query(js, jsEndBefore(document.js)).wrapped } - internal actual fun _endBefore(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndBefore(*fieldValues))) } + actual fun endBefore(vararg fieldValues: Any) = rethrow { query(js, jsEndBefore(*fieldValues)).wrapped } - internal actual fun _endAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndAt(document.js))) } + actual fun endAt(document: NativeDocumentSnapshot) = rethrow { query(js, jsEndAt(document.js)).wrapped } - internal actual fun _endAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndAt(*fieldValues))) } + actual fun endAt(vararg fieldValues: Any) = rethrow { query(js, jsEndAt(*fieldValues)).wrapped } actual val snapshots get() = callbackFlow { val unsubscribe = rethrow { @@ -366,8 +377,16 @@ actual open class Query internal actual constructor(nativeWrapper: NativeQueryWr } } +val Query.js get() = native.js + +actual data class NativeCollectionReference(override val js: JsCollectionReference) : NativeQuery(js) + @PublishedApi -internal actual class NativeCollectionReferenceWrapper(override val js: JsCollectionReference) : NativeQueryWrapper(js) { +internal actual class NativeCollectionReferenceWrapper actual internal constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + constructor(js: JsCollectionReference) : this(NativeCollectionReference(js)) + + override val js: JsCollectionReference = native.js actual val path: String get() = rethrow { js.path } @@ -378,12 +397,12 @@ internal actual class NativeCollectionReferenceWrapper(override val js: JsCollec actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } - actual suspend fun addEncoded(data: Any) = rethrow { - NativeDocumentReference(addDoc(js, data).await()) + actual suspend fun addEncoded(data: EncodedObject) = rethrow { + NativeDocumentReference(addDoc(js, data.json).await()) } } -val CollectionReference.js get() = nativeWrapper.js +val CollectionReference.js get() = native.js actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) @@ -409,8 +428,14 @@ actual class DocumentChange(val js: JsDocumentChange) { get() = ChangeType.values().first { it.jsString == js.type } } +actual data class NativeDocumentSnapshot(val js: JsDocumentSnapshot) + @PublishedApi -internal actual class NativeDocumentSnapshotWrapper(val js: JsDocumentSnapshot) { +internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: NativeDocumentSnapshot) { + + constructor(js: JsDocumentSnapshot) : this(NativeDocumentSnapshot(js)) + + val js: JsDocumentSnapshot = native.js actual val id get() = rethrow { js.id } actual val reference get() = rethrow { NativeDocumentReference(js.ref) } @@ -419,11 +444,16 @@ internal actual class NativeDocumentSnapshotWrapper(val js: JsDocumentSnapshot) js.get(field, getTimestampsOptions(serverTimestampBehavior)) } + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.get(fieldPath, getTimestampsOptions(serverTimestampBehavior)) + } + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { js.data(getTimestampsOptions(serverTimestampBehavior)) } actual fun contains(field: String) = rethrow { js.get(field) != undefined } + actual fun contains(fieldPath: EncodedFieldPath) = rethrow { js.get(fieldPath) != undefined } actual val exists get() = rethrow { js.exists() } actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) @@ -431,7 +461,7 @@ internal actual class NativeDocumentSnapshotWrapper(val js: JsDocumentSnapshot) json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) } -val DocumentSnapshot.js get() = nativeWrapper.js +val DocumentSnapshot.js get() = native.js actual class SnapshotMetadata(val js: JsSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = js.hasPendingWrites From 7941f11ed993ea6cb49e767436b4c1530cf2f3fd Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 12 Apr 2024 22:19:30 +0200 Subject: [PATCH 05/12] Cleanup & reenable tests --- .../kotlin/dev/gitlive/firebase/_encoders.kt | 3 +- .../kotlin/dev/gitlive/firebase/encoders.kt | 14 +- .../dev/gitlive/firebase/EncodersTest.kt | 26 ++ .../kotlin/dev/gitlive/firebase/_encoders.kt | 4 +- .../kotlin/dev/gitlive/firebase/_encoders.kt | 3 +- .../dev/gitlive/firebase/database/database.kt | 4 +- .../dev/gitlive/firebase/database/database.kt | 241 ++++++++++-------- .../dev/gitlive/firebase/database/database.kt | 4 +- .../dev/gitlive/firebase/database/database.kt | 5 +- .../gitlive/firebase/firestore/firestore.kt | 20 +- .../gitlive/firebase/firestore/firestore.kt | 32 +-- .../gitlive/firebase/firestore/firestore.kt | 14 +- 12 files changed, 212 insertions(+), 158 deletions(-) diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt index a13e9a859..0dfd63c8b 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,11 +10,10 @@ import kotlinx.serialization.descriptors.StructureKind import java.lang.IllegalArgumentException import kotlin.collections.set -actual data class EncodedObject(actual val raw: Map) { +actual data class EncodedObject(actual val raw: Map) : Map by raw { actual companion object { actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) } - val android: Map get() = raw } @PublishedApi diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index a74f2d24f..2c213d0e0 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -11,6 +11,12 @@ import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule +/** + * Platform specific object for storing encoded data that can be used for methods that explicitly require an object. + * This is essentially a [Map] of [String] and [Any]? (as represented by [raw]) but since [encode] gives a platform specific value, this method wraps that. + * + * Created using [encodeAsObject] + */ expect class EncodedObject { companion object { val emptyEncodedObject: EncodedObject @@ -40,12 +46,12 @@ inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) inline fun encodeAsObject(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { - val encoded = encode(strategy, value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") - return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") + val encoded = encode(strategy, value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") } inline fun encodeAsObject(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { - val encoded = encode(value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") - return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") + val encoded = encode(value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") } @PublishedApi diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index e393ebf56..e7c04e237 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -15,6 +15,7 @@ import kotlinx.serialization.modules.polymorphic import kotlin.jvm.JvmInline import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertNull @Serializable @@ -416,6 +417,31 @@ class EncodersTest { ) } + @Test + fun encodeAsObject() { + val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) + val encodedObject = encodeAsObject(TestData.serializer(), testDataClass) { encodeDefaults = false } + + assertEquals(mapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), encodedObject.raw) + + val testMap = mapOf("one" to 1, "two" to null, "three" to false) + assertEquals(testMap, encodeAsObject(testMap).raw) + + assertEquals(emptyMap(), encodeAsObject(TestObject).raw) + + assertFailsWith { encodeAsObject(true) } + assertFailsWith { encodeAsObject(42) } + assertFailsWith { encodeAsObject(8.toShort()) } + assertFailsWith { encodeAsObject(Int.MAX_VALUE.toLong() + 3) } + assertFailsWith { encodeAsObject(0x03F) } + assertFailsWith { encodeAsObject(3.33) } + assertFailsWith { encodeAsObject(6.65f) } + assertFailsWith { encodeAsObject("Test") } + assertFailsWith { encodeAsObject(ValueClass(2)) } + assertFailsWith { encodeAsObject(mapOf(1 to "one")) } + assertFailsWith { encodeAsObject(listOf("one")) } + } + private inline fun assertEncode(value: T) { val encoded = encode(value) assertEquals(value, encoded) diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt index da99abbdc..e049451f2 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -9,12 +9,10 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual data class EncodedObject(actual val raw: Map) { +actual data class EncodedObject(actual val raw: Map) : Map by raw.mapKeys({ (key, _) -> key as? Any }) { actual companion object { actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) } - - val ios: Map get() = raw.mapKeys { (key, _) -> key as? Any } } @PublishedApi diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index 00cafbce8..c0245f1b0 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,13 +10,12 @@ import kotlinx.serialization.descriptors.StructureKind import kotlin.js.Json import kotlin.js.json -actual data class EncodedObject(private val keyValues: List>) { +actual data class EncodedObject(private val keyValues: List>) : Json by json(*keyValues.toTypedArray()) { actual companion object { actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyList()) } actual val raw get() = keyValues.toMap() - val json get() = json(*keyValues.toTypedArray()) } diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index 9f2a4a0e5..98b3b5a99 100644 --- a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -207,7 +207,7 @@ internal actual class NativeDatabaseReference internal constructor( .run { Unit } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - android.updateChildren(encodedUpdate.android) + android.updateChildren(encodedUpdate) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @@ -297,7 +297,7 @@ internal actual class NativeOnDisconnect internal constructor( .run { Unit } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - android.updateChildren(encodedUpdate.android) + android.updateChildren(encodedUpdate) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } } diff --git a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt index 60c209fa3..568d0868d 100644 --- a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -17,6 +17,7 @@ import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.time.Duration.Companion.minutes @@ -62,116 +63,142 @@ class FirebaseDatabaseTest { } } -// @Test -// fun testSetValue() = runTest { -// ensureDatabaseConnected() -// val testValue = "test" -// val testReference = database.reference("testPath") -// -// testReference.setValue(testValue) -// -// val testReferenceValue = testReference -// .valueEvents -// .first() -// .value() -// -// assertEquals(testValue, testReferenceValue) -// } -// -// @Test -// fun testChildCount() = runTest { -// setupRealtimeData() -// val dataSnapshot = database -// .reference("FirebaseRealtimeDatabaseTest") -// .valueEvents -// .first() -// -// val firebaseDatabaseChildCount = dataSnapshot.children.count() -// assertEquals(3, firebaseDatabaseChildCount) -// } -// -// @Test -// fun testBasicIncrementTransaction() = runTest { -// ensureDatabaseConnected() -// val data = DatabaseTest("PostOne", 2) -// val userRef = database.reference("users/user_1/post_id_1") -// setupDatabase(userRef, data, DatabaseTest.serializer()) -// -// // Check database before transaction -// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) -// assertEquals(data.title, userDocBefore.title) -// assertEquals(data.likes, userDocBefore.likes) -// -// // Run transaction -// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes + 1) } -// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) -// -// // Check the database after transaction -// assertEquals(data.title, userDocAfter.title) -// assertEquals(data.likes + 1, userDocAfter.likes) -// } -// -// @Test -// fun testBasicDecrementTransaction() = runTest { -// ensureDatabaseConnected() -// val data = DatabaseTest("PostTwo", 2) -// val userRef = database.reference("users/user_1/post_id_2") -// setupDatabase(userRef, data, DatabaseTest.serializer()) -// -// // Check database before transaction -// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) -// assertEquals(data.title, userDocBefore.title) -// assertEquals(data.likes, userDocBefore.likes) -// -// // Run transaction -// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes - 1) } -// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) -// -// // Check the database after transaction -// assertEquals(data.title, userDocAfter.title) -// assertEquals(data.likes - 1, userDocAfter.likes) -// } -// -// @Test -// fun testSetServerTimestamp() = runTest { -// ensureDatabaseConnected() -// val testReference = database.reference("testSetServerTimestamp") -// -// testReference.setValue(ServerValue.TIMESTAMP) -// -// val timestamp = testReference -// .valueEvents -// .first() -// .value() -// -// assertTrue(timestamp > 0) -// } -// -// @Test -// fun testIncrement() = runTest { -// ensureDatabaseConnected() -// val testReference = database.reference("testIncrement") -// -// testReference.setValue(2.0) -// -// val value = testReference -// .valueEvents -// .first() -// .value() -// -// assertEquals(2.0, value) -// -// testReference.setValue(ServerValue.increment(5.0)) -// val updatedValue = testReference -// .valueEvents -// .first() -// .value() -// -// assertEquals(7.0, updatedValue) -// } + @Test + fun testSetValue() = runTest { + ensureDatabaseConnected() + val testValue = "test" + val testReference = database.reference("testPath") + + testReference.setValue(testValue) + + val testReferenceValue = testReference + .valueEvents + .first() + .value() + + assertEquals(testValue, testReferenceValue) + } + + @Test + fun testChildCount() = runTest { + setupRealtimeData() + val dataSnapshot = database + .reference("FirebaseRealtimeDatabaseTest") + .valueEvents + .first() + + val firebaseDatabaseChildCount = dataSnapshot.child("values").children.count() + assertEquals(3, firebaseDatabaseChildCount) + } + + @Test + fun testBasicIncrementTransaction() = runTest { + ensureDatabaseConnected() + val data = DatabaseTest("PostOne", 2) + val userRef = database.reference("users/user_1/post_id_1") + setupDatabase(userRef, data, DatabaseTest.serializer()) + + // Check database before transaction + val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) + assertEquals(data.title, userDocBefore.title) + assertEquals(data.likes, userDocBefore.likes) + + // Run transaction + val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes + 1) } + val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) + + // Check the database after transaction + assertEquals(data.title, userDocAfter.title) + assertEquals(data.likes + 1, userDocAfter.likes) + } + + @Test + fun testBasicDecrementTransaction() = runTest { + ensureDatabaseConnected() + val data = DatabaseTest("PostTwo", 2) + val userRef = database.reference("users/user_1/post_id_2") + setupDatabase(userRef, data, DatabaseTest.serializer()) + + // Check database before transaction + val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) + assertEquals(data.title, userDocBefore.title) + assertEquals(data.likes, userDocBefore.likes) + + // Run transaction + val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes - 1) } + val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) + + // Check the database after transaction + assertEquals(data.title, userDocAfter.title) + assertEquals(data.likes - 1, userDocAfter.likes) + } + + @Test + fun testSetServerTimestamp() = runTest { + ensureDatabaseConnected() + val testReference = database.reference("testSetServerTimestamp") + + testReference.setValue(ServerValue.TIMESTAMP) + + val timestamp = testReference + .valueEvents + .first() + .value() + + assertTrue(timestamp > 0) + } + + @Test + fun testIncrement() = runTest { + ensureDatabaseConnected() + val testReference = database.reference("testIncrement") + + testReference.setValue(2.0) + + val value = testReference + .valueEvents + .first() + .value() + + assertEquals(2.0, value) + + testReference.setValue(ServerValue.increment(5.0)) + val updatedValue = testReference + .valueEvents + .first() + .value() + + assertEquals(7.0, updatedValue) + } + + @Test + fun testBreakRules() = runTest { + ensureDatabaseConnected() + val reference = database + .reference("FirebaseRealtimeDatabaseTest") + val child = reference.child("lastActivity") + assertFailsWith { + child.setValue("stringNotAllowed") + } + child.setValue(2) + assertFailsWith { + reference.updateChildren(mapOf("lastActivity" to "stringNotAllowed")) + } + } @Test fun testUpdateChildren() = runTest { + setupRealtimeData() + val reference = database + .reference("FirebaseRealtimeDatabaseTest") + val valueEvents = reference.child("lastActivity").valueEvents + assertTrue(valueEvents.first().exists) + reference.updateChildren(mapOf("test" to false, "nested" to mapOf("lastActivity" to null), "lastActivity" to null)) + assertFalse(valueEvents.first().exists) + } + + @Test + fun testUpdateChildrenOnDisconnect() = runTest { setupRealtimeData() val reference = database .reference("FirebaseRealtimeDatabaseTest") diff --git a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt index d53746b25..64f551dc0 100644 --- a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -170,7 +170,7 @@ internal actual class NativeDatabaseReference internal constructor( } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { - ios.await(persistenceEnabled) { updateChildValues(encodedUpdate.ios, it) } + ios.await(persistenceEnabled) { updateChildValues(encodedUpdate, it) } } actual suspend fun removeValue() { @@ -241,7 +241,7 @@ internal actual class NativeOnDisconnect internal constructor( } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { - ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate.ios, it) } + ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate, it) } } } diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index 4e48d612c..bbd17ddd1 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -162,7 +162,6 @@ actual open class Query internal actual constructor( actual fun equalTo(value: Boolean, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database) override fun toString() = js.toString() - } @PublishedApi @@ -184,7 +183,7 @@ internal actual class NativeDatabaseReference internal constructor( } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { update(js, encodedUpdate.json).awaitWhileOnline(database) } + rethrow { update(js, encodedUpdate).awaitWhileOnline(database) } actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { @@ -236,7 +235,7 @@ internal actual class NativeOnDisconnect internal constructor( rethrow { js.set(encodedValue).awaitWhileOnline(database) } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { js.update(encodedUpdate.json).awaitWhileOnline(database) } + rethrow { js.update(encodedUpdate).awaitWhileOnline(database) } } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index c82d12440..a035a9957 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -84,12 +84,12 @@ internal actual class NativeWriteBatchWrapper actual internal constructor(actual encodedData: EncodedObject, setOptions: SetOptions ): NativeWriteBatchWrapper = (setOptions.android?.let { - native.set(documentRef.android, encodedData.android, it) - } ?: native.set(documentRef.android, encodedData.android)).let { + native.set(documentRef.android, encodedData, it) + } ?: native.set(documentRef.android, encodedData)).let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, @@ -126,12 +126,12 @@ internal actual class NativeTransactionWrapper actual internal constructor(actua setOptions: SetOptions ): NativeTransactionWrapper { setOptions.android?.let { - native.set(documentRef.android, encodedData.android, it) - } ?: native.set(documentRef.android, encodedData.android) + native.set(documentRef.android, encodedData, it) + } ?: native.set(documentRef.android, encodedData) return this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, @@ -178,13 +178,13 @@ internal actual class NativeDocumentReference actual constructor(actual val nati actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) { val task = (setOptions.android?.let { - android.set(encodedData.android, it) - } ?: android.set(encodedData.android)) + android.set(encodedData, it) + } ?: android.set(encodedData)) task.await() } actual suspend fun updateEncoded(encodedData: EncodedObject) { - android.update(encodedData.android).await() + android.update(encodedData).await() } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { @@ -350,7 +350,7 @@ internal actual class NativeCollectionReferenceWrapper internal actual construct actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(native.add(data.android).await()) + actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(native.add(data).await()) } val CollectionReference.android get() = native diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index ec87011db..17f021f6a 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -77,13 +77,13 @@ internal actual class NativeWriteBatchWrapper actual constructor(actual val nati encodedData: EncodedObject, setOptions: SetOptions ): NativeWriteBatchWrapper = when (setOptions) { - is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) - is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) - is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) + is SetOptions.Merge -> native.setData(encodedData, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData, documentRef.ios, setOptions.encodedFieldPaths) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData, documentRef.ios).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, @@ -119,13 +119,13 @@ internal actual class NativeTransactionWrapper actual constructor(actual val nat encodedData: EncodedObject, setOptions: SetOptions ): NativeTransactionWrapper = when (setOptions) { - is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) - is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) - is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) + is SetOptions.Merge -> native.setData(encodedData, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData, documentRef.ios, setOptions.encodedFieldPaths) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData, documentRef.ios).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, @@ -186,15 +186,15 @@ internal actual class NativeDocumentReference actual constructor(actual val nati actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = await { when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData.ios, true, it) - is SetOptions.Overwrite -> ios.setData(encodedData.ios, false, it) - is SetOptions.MergeFields -> ios.setData(encodedData.ios, setOptions.fields, it) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData.ios, setOptions.encodedFieldPaths, it) + is SetOptions.Merge -> ios.setData(encodedData, true, it) + is SetOptions.Overwrite -> ios.setData(encodedData, false, it) + is SetOptions.MergeFields -> ios.setData(encodedData, setOptions.fields, it) + is SetOptions.MergeFieldPaths -> ios.setData(encodedData, setOptions.encodedFieldPaths, it) } } actual suspend fun updateEncoded(encodedData: EncodedObject) = await { - ios.updateData(encodedData.ios, it) + ios.updateData(encodedData, it) } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = await { @@ -310,7 +310,7 @@ internal actual class NativeCollectionReferenceWrapper internal actual construct actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { native.addDocumentWithData(data.ios, it) }) + actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { native.addDocumentWithData(data, it) }) } val CollectionReference.ios get() = native diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index c6b3effff..d6b8ab1a1 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -131,9 +131,9 @@ internal actual class NativeWriteBatchWrapper actual internal constructor(actual documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions - ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.json, setOptions.js) }.let { this } + ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.json) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -177,11 +177,11 @@ internal actual class NativeTransactionWrapper actual internal constructor(actua encodedData: EncodedObject, setOptions: SetOptions ): NativeTransactionWrapper = rethrow { - js.set(documentRef.js, encodedData.json, setOptions.js) + js.set(documentRef.js, encodedData, setOptions.js) } .let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.json) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -245,10 +245,10 @@ internal actual class NativeDocumentReference actual constructor(actual val nati } actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { - setDoc(js, encodedData.json, setOptions.js).await() + setDoc(js, encodedData, setOptions.js).await() } - actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData.json).await() } + actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData).await() } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { rethrow { @@ -398,7 +398,7 @@ internal actual class NativeCollectionReferenceWrapper actual internal construct actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } actual suspend fun addEncoded(data: EncodedObject) = rethrow { - NativeDocumentReference(addDoc(js, data.json).await()) + NativeDocumentReference(addDoc(js, data).await()) } } From 32ac02ff6b8acd039f56bb98e5c2d21a99d6cea8 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 12 Apr 2024 22:49:00 +0200 Subject: [PATCH 06/12] Fixing some JS issues --- .../kotlin/dev/gitlive/firebase/encoders.kt | 6 +++++ .../dev/gitlive/firebase/EncodersTest.kt | 2 +- .../kotlin/dev/gitlive/firebase/_encoders.kt | 25 ++++++++++++++++--- .../dev/gitlive/firebase/database/database.kt | 4 +-- .../gitlive/firebase/firestore/firestore.kt | 14 +++++------ 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 2c213d0e0..9b07bee13 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -46,10 +46,16 @@ inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) inline fun encodeAsObject(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { + if (value is Map<*, *> && value.keys.any { it !is String }) { + throw IllegalArgumentException("$value is a Map containing non-String keys. Must be of the form Map") + } val encoded = encode(strategy, value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") } inline fun encodeAsObject(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { + if (value is Map<*, *> && value.keys.any { it !is String }) { + throw IllegalArgumentException("$value is a Map containing non-String keys. Must be of the form Map") + } val encoded = encode(value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") } diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index e7c04e237..3c3c7b420 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -422,7 +422,7 @@ class EncodersTest { val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) val encodedObject = encodeAsObject(TestData.serializer(), testDataClass) { encodeDefaults = false } - assertEquals(mapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), encodedObject.raw) + nativeAssertEquals(mapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), encodedObject.raw) val testMap = mapOf("one" to 1, "two" to null, "three" to false) assertEquals(testMap, encodeAsObject(testMap).raw) diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index c0245f1b0..b9e5615d6 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,25 +10,42 @@ import kotlinx.serialization.descriptors.StructureKind import kotlin.js.Json import kotlin.js.json -actual data class EncodedObject(private val keyValues: List>) : Json by json(*keyValues.toTypedArray()) { +actual data class EncodedObject(private val keyValues: List>) { actual companion object { actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyList()) } actual val raw get() = keyValues.toMap() + val json get() = json(*keyValues.toTypedArray()) } - @PublishedApi internal actual fun List>.asEncodedObject() = EncodedObject(this) @PublishedApi -internal actual fun Any.asNativeMap(): Map<*, *>? = (this as? Json)?.let { json -> +internal actual fun Any.asNativeMap(): Map<*, *>? { + val json = when (this) { + is Number -> null + is Boolean -> null + is String -> null + is Map<*, *> -> { + if (keys.all { it is String }) { + this as Json + } else { + null + } + } + is Collection<*> -> null + is Array<*> -> null + else -> { + this as Json + } + } ?: return null val mutableMap = mutableMapOf() for (key in js("Object").keys(json)) { mutableMap[key] = json[key] } - mutableMap.toMap() + return mutableMap.toMap() } actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index bbd17ddd1..ba3e720b2 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -183,7 +183,7 @@ internal actual class NativeDatabaseReference internal constructor( } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { update(js, encodedUpdate).awaitWhileOnline(database) } + rethrow { update(js, encodedUpdate.json).awaitWhileOnline(database) } actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { @@ -235,7 +235,7 @@ internal actual class NativeOnDisconnect internal constructor( rethrow { js.set(encodedValue).awaitWhileOnline(database) } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { js.update(encodedUpdate).awaitWhileOnline(database) } + rethrow { js.update(encodedUpdate.json).awaitWhileOnline(database) } } diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index d6b8ab1a1..c6b3effff 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -131,9 +131,9 @@ internal actual class NativeWriteBatchWrapper actual internal constructor(actual documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions - ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } + ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.json, setOptions.js) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.json) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -177,11 +177,11 @@ internal actual class NativeTransactionWrapper actual internal constructor(actua encodedData: EncodedObject, setOptions: SetOptions ): NativeTransactionWrapper = rethrow { - js.set(documentRef.js, encodedData, setOptions.js) + js.set(documentRef.js, encodedData.json, setOptions.js) } .let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.json) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -245,10 +245,10 @@ internal actual class NativeDocumentReference actual constructor(actual val nati } actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { - setDoc(js, encodedData, setOptions.js).await() + setDoc(js, encodedData.json, setOptions.js).await() } - actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData).await() } + actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData.json).await() } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { rethrow { @@ -398,7 +398,7 @@ internal actual class NativeCollectionReferenceWrapper actual internal construct actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } actual suspend fun addEncoded(data: EncodedObject) = rethrow { - NativeDocumentReference(addDoc(js, data).await()) + NativeDocumentReference(addDoc(js, data.json).await()) } } From 5dc2e4b03164465df938f0e102f0842229b96eb9 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sat, 13 Apr 2024 08:08:12 +0200 Subject: [PATCH 07/12] Restricted access to creating an EncodedObject --- .../kotlin/dev/gitlive/firebase/_encoders.kt | 6 +----- .../kotlin/dev/gitlive/firebase/encoders.kt | 17 ++++++++++++----- .../kotlin/dev/gitlive/firebase/_encoders.kt | 6 +----- .../kotlin/dev/gitlive/firebase/_encoders.kt | 6 +----- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt index 0dfd63c8b..c1f401d95 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,11 +10,7 @@ import kotlinx.serialization.descriptors.StructureKind import java.lang.IllegalArgumentException import kotlin.collections.set -actual data class EncodedObject(actual val raw: Map) : Map by raw { - actual companion object { - actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) - } -} +actual data class EncodedObject internal constructor(actual val raw: Map) : Map by raw @PublishedApi internal actual fun List>.asEncodedObject() = EncodedObject(toMap()) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 9b07bee13..58069a943 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -15,13 +15,9 @@ import kotlinx.serialization.modules.SerializersModule * Platform specific object for storing encoded data that can be used for methods that explicitly require an object. * This is essentially a [Map] of [String] and [Any]? (as represented by [raw]) but since [encode] gives a platform specific value, this method wraps that. * - * Created using [encodeAsObject] + * Created using [encodeAsObject]. It is not recommended to encode to this manually. */ expect class EncodedObject { - companion object { - val emptyEncodedObject: EncodedObject - } - val raw: Map } @@ -45,6 +41,11 @@ inline fun encode(value: T, shouldEncodeElementDefault: Boolean): An inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) +/** + * Encodes data as an [EncodedObject]. + * This is not recommended for manual use, but may be done by the library internally. + * @throws IllegalArgumentException if [value] is not valid as an [EncodedObject] (e.g. not encodable in the form Map + */ inline fun encodeAsObject(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { if (value is Map<*, *> && value.keys.any { it !is String }) { throw IllegalArgumentException("$value is a Map containing non-String keys. Must be of the form Map") @@ -52,6 +53,12 @@ inline fun encodeAsObject(strategy: SerializationStrategy, value: T val encoded = encode(strategy, value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") } + +/** + * Encodes data as an [EncodedObject]. + * This is not recommended for manual use, but may be done by the library internally. + * @throws IllegalArgumentException if [value] is not valid as an [EncodedObject] (e.g. not encodable in the form Map + */ inline fun encodeAsObject(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { if (value is Map<*, *> && value.keys.any { it !is String }) { throw IllegalArgumentException("$value is a Map containing non-String keys. Must be of the form Map") diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt index e049451f2..0a43ec9c7 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -9,11 +9,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual data class EncodedObject(actual val raw: Map) : Map by raw.mapKeys({ (key, _) -> key as? Any }) { - actual companion object { - actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) - } -} +actual data class EncodedObject internal constructor(actual val raw: Map) : Map by raw.mapKeys({ (key, _) -> key as? Any }) @PublishedApi internal actual fun List>.asEncodedObject() = EncodedObject(toMap()) diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index b9e5615d6..ae282fd1a 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,11 +10,7 @@ import kotlinx.serialization.descriptors.StructureKind import kotlin.js.Json import kotlin.js.json -actual data class EncodedObject(private val keyValues: List>) { - actual companion object { - actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyList()) - } - +actual class EncodedObject internal constructor(private val keyValues: List>) { actual val raw get() = keyValues.toMap() val json get() = json(*keyValues.toTypedArray()) } From a371444d26389022f6c079281e924a67c4f65188 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sat, 13 Apr 2024 10:16:15 +0200 Subject: [PATCH 08/12] EncodedObject as interface --- .../kotlin/dev/gitlive/firebase/_encoders.kt | 9 +++++++-- .../kotlin/dev/gitlive/firebase/encoders.kt | 6 +++--- .../kotlin/dev/gitlive/firebase/_encoders.kt | 11 +++++++++-- .../jsMain/kotlin/dev/gitlive/firebase/_encoders.kt | 13 +++++++++---- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt index c1f401d95..6ade4a9e0 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,10 +10,15 @@ import kotlinx.serialization.descriptors.StructureKind import java.lang.IllegalArgumentException import kotlin.collections.set -actual data class EncodedObject internal constructor(actual val raw: Map) : Map by raw +actual interface EncodedObject : Map { + actual val raw: Map +} + +@PublishedApi +internal data class EncodedObjectImpl internal constructor(override val raw: Map) : EncodedObject, Map by raw @PublishedApi -internal actual fun List>.asEncodedObject() = EncodedObject(toMap()) +internal actual fun Map.asEncodedObject(): EncodedObject = EncodedObjectImpl(this) @PublishedApi internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 58069a943..54a3d4472 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.modules.SerializersModule * * Created using [encodeAsObject]. It is not recommended to encode to this manually. */ -expect class EncodedObject { +expect interface EncodedObject { val raw: Map } @@ -91,10 +91,10 @@ internal fun Map<*, *>.asEncodedObject(): EncodedObject = map { (key, value) -> } else { throw IllegalArgumentException("Expected a String key but received $key") } -}.asEncodedObject() +}.toMap().asEncodedObject() @PublishedApi -internal expect fun List>.asEncodedObject(): EncodedObject +internal expect fun Map.asEncodedObject(): EncodedObject /** * An extension which which serializer to use for value. Handy in updating fields by name or path diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt index 0a43ec9c7..aaa584adb 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -9,10 +9,17 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual data class EncodedObject internal constructor(actual val raw: Map) : Map by raw.mapKeys({ (key, _) -> key as? Any }) +actual interface EncodedObject : Map { + actual val raw: Map +} + +@PublishedApi +internal data class InternalEncodedObject internal constructor( + override val raw: Map +) : EncodedObject, Map by raw.mapKeys({ (key, _) -> key }) @PublishedApi -internal actual fun List>.asEncodedObject() = EncodedObject(toMap()) +internal actual fun Map.asEncodedObject(): EncodedObject = InternalEncodedObject(this) @PublishedApi internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index ae282fd1a..b742128cf 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,13 +10,18 @@ import kotlinx.serialization.descriptors.StructureKind import kotlin.js.Json import kotlin.js.json -actual class EncodedObject internal constructor(private val keyValues: List>) { - actual val raw get() = keyValues.toMap() - val json get() = json(*keyValues.toTypedArray()) +actual interface EncodedObject { + actual val raw: Map + val json: Json } @PublishedApi -internal actual fun List>.asEncodedObject() = EncodedObject(this) +internal class InternalEncodedObject internal constructor(override val raw: Map) : EncodedObject { + override val json: Json get() = json(*raw.entries.map { (key, value) -> key to value }.toTypedArray()) +} + +@PublishedApi +internal actual fun Map.asEncodedObject(): EncodedObject = InternalEncodedObject(this) @PublishedApi internal actual fun Any.asNativeMap(): Map<*, *>? { From 9f7d3cbf4eb41084c1ceb439417f89882c4397a4 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sat, 13 Apr 2024 12:03:04 +0200 Subject: [PATCH 09/12] Move parts of the firebase-common module to new firebase-common-internal New module to explicitly mark difference between Common properties that are public facing and those that should be internal --- firebase-app/package.json | 4 +- .../kotlin/dev/gitlive/firebase/auth/auth.kt | 7 +- .../kotlin/dev/gitlive/firebase/auth/auth.kt | 4 +- firebase-common-internal/build.gradle.kts | 182 ++++++++++++++++++ firebase-common-internal/package.json | 32 +++ .../gitlive/firebase/internal}/_decoders.kt | 2 +- .../gitlive/firebase/internal}/_encoders.kt | 8 +- .../firebase/internal/EncodeDecodeSettings.kt | 43 +++++ .../gitlive/firebase/internal}/Polymorphic.kt | 3 +- .../gitlive/firebase/internal}/decoders.kt | 9 +- .../gitlive/firebase/internal}/encoders.kt | 19 +- .../internal}/reencodeTransformation.kt | 3 +- .../gitlive/firebase/internal}/serializers.kt | 2 +- .../firebase/internal}/EncodersTest.kt | 52 ++++- .../gitlive/firebase/internal}/_decoders.kt | 2 +- .../gitlive/firebase/internal}/_encoders.kt | 7 +- .../gitlive/firebase/internal}/_decoders.kt | 2 +- .../gitlive/firebase/internal}/_encoders.kt | 11 +- firebase-common/package.json | 2 +- .../dev/gitlive/firebase/EncodedObject.kt | 9 + .../gitlive/firebase/EncodeDecodeSettings.kt | 39 +--- .../dev/gitlive/firebase/EncodedObject.kt | 13 ++ .../dev/gitlive/firebase/EncodedObject.kt | 9 + .../dev/gitlive/firebase/EncodedObject.kt | 12 ++ firebase-config/package.json | 4 +- firebase-crashlytics/package.json | 2 +- firebase-database/build.gradle.kts | 3 +- firebase-database/package.json | 4 +- .../dev/gitlive/firebase/database/database.kt | 4 +- .../gitlive/firebase/database/ServerValue.kt | 6 +- .../dev/gitlive/firebase/database/database.kt | 4 +- .../dev/gitlive/firebase/database/database.kt | 4 +- .../dev/gitlive/firebase/database/database.kt | 4 +- .../firebase/database/externals/database.kt | 2 +- .../dev/gitlive/firebase/database/database.kt | 1 - firebase-firestore/build.gradle.kts | 3 +- firebase-firestore/package.json | 4 +- .../firestore/DocumentReferenceSerializer.kt | 4 +- .../firestore/FieldValueSerializer.kt | 4 +- .../firebase/firestore/GeoPointSerializer.kt | 2 +- .../firebase/firestore/TimestampSerializer.kt | 3 +- .../gitlive/firebase/firestore/encoders.kt | 2 +- .../gitlive/firebase/firestore/firestore.kt | 9 +- .../firebase/firestore/FieldValueTests.kt | 2 +- .../firebase/firestore/GeoPointTests.kt | 4 +- .../firebase/firestore/TimestampTests.kt | 6 +- .../gitlive/firebase/firestore/firestore.kt | 4 +- .../gitlive/firebase/firestore/GeoPoint.kt | 1 - .../gitlive/firebase/firestore/firestore.kt | 5 +- .../firebase/firestore/ContextSwitchTest.kt | 7 +- firebase-functions/build.gradle.kts | 3 +- firebase-functions/package.json | 4 +- .../gitlive/firebase/functions/functions.kt | 2 +- .../gitlive/firebase/functions/functions.kt | 7 +- .../gitlive/firebase/functions/functions.kt | 7 +- .../gitlive/firebase/functions/functions.kt | 14 +- firebase-installations/package.json | 2 +- .../firebase/installations/installations.kt | 4 +- firebase-perf/package.json | 2 +- .../gitlive/firebase/perf/metrics/Trace.kt | 5 +- .../gitlive/firebase/perf/metrics/Trace.kt | 5 +- .../dev/gitlive/firebase/perf/performance.kt | 9 +- .../gitlive/firebase/perf/metrics/Trace.kt | 6 +- firebase-storage/package.json | 2 +- gradle.properties | 3 + settings.gradle.kts | 3 + 66 files changed, 502 insertions(+), 150 deletions(-) create mode 100644 firebase-common-internal/build.gradle.kts create mode 100644 firebase-common-internal/package.json rename {firebase-common/src/androidMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal}/_decoders.kt (97%) rename {firebase-common/src/androidMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal}/_encoders.kt (91%) create mode 100644 firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodeDecodeSettings.kt rename {firebase-common/src/commonMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal}/Polymorphic.kt (96%) rename {firebase-common/src/commonMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal}/decoders.kt (97%) rename {firebase-common/src/commonMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal}/encoders.kt (93%) rename {firebase-common/src/commonMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal}/reencodeTransformation.kt (90%) rename {firebase-common/src/commonMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal}/serializers.kt (99%) rename {firebase-common/src/commonTest/kotlin/dev/gitlive/firebase => firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal}/EncodersTest.kt (93%) rename {firebase-common/src/iosMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal}/_decoders.kt (97%) rename {firebase-common/src/iosMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal}/_encoders.kt (94%) rename {firebase-common/src/jsMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal}/_decoders.kt (98%) rename {firebase-common/src/jsMain/kotlin/dev/gitlive/firebase => firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal}/_encoders.kt (94%) create mode 100644 firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/EncodedObject.kt create mode 100644 firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodedObject.kt create mode 100644 firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/EncodedObject.kt create mode 100644 firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/EncodedObject.kt diff --git a/firebase-app/package.json b/firebase-app/package.json index 81138414d..397e36723 100644 --- a/firebase-app/package.json +++ b/firebase-app/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-app", - "version": "1.11.1", + "version": "1.12.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-app.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-common": "1.11.1", + "@gitlive/firebase-common": "1.12.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt index ceaf34929..0c36bea7b 100644 --- a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -4,7 +4,12 @@ package dev.gitlive.firebase.auth -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest +import dev.gitlive.firebase.runTest import kotlin.random.Random import kotlin.test.* diff --git a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt index 9bfc2e61b..f2db371b4 100644 --- a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -4,8 +4,10 @@ package dev.gitlive.firebase.auth -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.FirebaseNetworkException import dev.gitlive.firebase.auth.externals.* import kotlinx.coroutines.await import kotlinx.coroutines.channels.awaitClose diff --git a/firebase-common-internal/build.gradle.kts b/firebase-common-internal/build.gradle.kts new file mode 100644 index 000000000..8a62d7c47 --- /dev/null +++ b/firebase-common-internal/build.gradle.kts @@ -0,0 +1,182 @@ +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree + +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +version = project.property("firebase-common-internal.version") as String + +plugins { + id("com.android.library") + kotlin("multiplatform") + kotlin("plugin.serialization") +} + +android { + val minSdkVersion: Int by project + val compileSdkVersion: Int by project + + compileSdk = compileSdkVersion + namespace = "dev.gitlive.firebase.common.internal" + defaultConfig { + minSdk = minSdkVersion + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + testOptions { + unitTests.apply { + isIncludeAndroidResources = true + } + } + + packaging { + resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") + resources.pickFirsts.add("META-INF/AL2.0") + resources.pickFirsts.add("META-INF/LGPL2.1") + } + lint { + abortOnError = false + } +} + +kotlin { + + targets.configureEach { + compilations.configureEach { + kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + } + } + + @Suppress("OPT_IN_USAGE") + androidTarget { + instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) + unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) + publishAllLibraryVariants() + compilations.configureEach { + kotlinOptions { + jvmTarget = "11" + } + } + } + + jvm { + compilations.getByName("main") { + kotlinOptions { + jvmTarget = "17" + } + } + compilations.getByName("test") { + kotlinOptions { + jvmTarget = "17" + } + } + } + + val supportIosTarget = project.property("skipIosTarget") != "true" + + if (supportIosTarget) { + iosArm64() + iosX64() + iosSimulatorArm64() + } + + js(IR) { + useCommonJs() + nodejs { + testTask( + Action { + useKarma { + useChromeHeadless() + } + } + ) + } + browser { + testTask( + Action { + useKarma { + useChromeHeadless() + } + } + ) + } + } + + sourceSets { + all { + languageSettings.apply { + val apiVersion: String by project + val languageVersion: String by project + this.apiVersion = apiVersion + this.languageVersion = languageVersion + progressiveMode = true + optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") + optIn("kotlinx.serialization.ExperimentalSerializationApi") + optIn("kotlinx.serialization.InternalSerializationApi") + } + } + + getByName("commonMain") { + val serializationVersion: String by project + + dependencies { + implementation(project(":firebase-common")) + api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") + } + } + + getByName("commonTest") { + dependencies { + implementation(project(":test-utils")) + } + } + + getByName("androidMain") { + dependencies { + api("com.google.firebase:firebase-common-ktx") + } + } + + getByName("jsMain") { + dependencies { + api(npm("firebase", "10.6.0")) + } + } + + getByName("jvmMain") { + kotlin.srcDir("src/androidMain/kotlin") + } + + getByName("jvmTest") { + dependencies { + implementation(kotlin("test-junit")) + } + kotlin.srcDir("src/androidAndroidTest/kotlin") + } + } +} + +if (project.property("firebase-common.skipIosTests") == "true") { + tasks.forEach { + if (it.name.contains("ios", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +if (project.property("firebase-common.skipJsTests") == "true") { + tasks.forEach { + if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +signing { + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications) +} + diff --git a/firebase-common-internal/package.json b/firebase-common-internal/package.json new file mode 100644 index 000000000..e5bfd8389 --- /dev/null +++ b/firebase-common-internal/package.json @@ -0,0 +1,32 @@ +{ + "name": "@gitlive/firebase-common-internal", + "version": "1.12.0", + "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", + "main": "firebase-common-internal.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/GitLiveApp/firebase-kotlin-sdk.git" + }, + "keywords": [ + "kotlin", + "multiplatform", + "kotlin-js", + "firebase" + ], + "author": "dev.gitlive", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/GitLiveApp/firebase-kotlin-multiplatform-sdk/issues" + }, + "homepage": "https://github.com/GitLiveApp/firebase-kotlin-multiplatform-sdk", + "dependencies": { + "@gitlive/firebase-common": "1.12.0", + "firebase": "9.19.1", + "kotlin": "1.8.20", + "kotlinx-coroutines-core": "1.6.4", + "kotlinx-serialization-kotlinx-serialization-runtime": "1.3.2" + } +} diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt similarity index 97% rename from firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt rename to firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt index ae930a08a..83b71cad6 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt @@ -2,7 +2,7 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt similarity index 91% rename from firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt rename to firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt index 6ade4a9e0..0a8ce569b 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt @@ -2,18 +2,14 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.EncodedObject import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind -import java.lang.IllegalArgumentException import kotlin.collections.set -actual interface EncodedObject : Map { - actual val raw: Map -} - @PublishedApi internal data class EncodedObjectImpl internal constructor(override val raw: Map) : EncodedObject, Map by raw diff --git a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodeDecodeSettings.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodeDecodeSettings.kt new file mode 100644 index 000000000..0a1fdda8f --- /dev/null +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodeDecodeSettings.kt @@ -0,0 +1,43 @@ +package dev.gitlive.firebase.internal + +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import dev.gitlive.firebase.EncodeSettings +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule + +@PublishedApi +internal data class EncodeSettingsImpl internal constructor( + override val encodeDefaults: Boolean, + override val serializersModule: SerializersModule, +) : EncodeSettings { + + @PublishedApi + internal class Builder : EncodeSettings.Builder { + override var encodeDefaults: Boolean = true + override var serializersModule: SerializersModule = EmptySerializersModule() + } +} + +@PublishedApi +internal class DecodeSettingsImpl internal constructor( + override val serializersModule: SerializersModule = EmptySerializersModule(), +) : DecodeSettings { + + @PublishedApi + internal class Builder : DecodeSettings.Builder { + override var serializersModule: SerializersModule = EmptySerializersModule() + } +} + +@PublishedApi +internal class EncodeDecodeSettingsBuilderImpl : EncodeDecodeSettingsBuilder { + + override var encodeDefaults: Boolean = true + override var serializersModule: SerializersModule = EmptySerializersModule() +} + +@PublishedApi +internal fun EncodeSettings.Builder.buildEncodeSettings(): EncodeSettings = EncodeSettingsImpl(encodeDefaults, serializersModule) +@PublishedApi +internal fun DecodeSettings.Builder.buildDecodeSettings(): DecodeSettings = DecodeSettingsImpl(serializersModule) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/Polymorphic.kt similarity index 96% rename from firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt rename to firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/Polymorphic.kt index 41563d527..5998f26cb 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/Polymorphic.kt @@ -1,5 +1,6 @@ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.FirebaseClassDiscriminator import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.SerialDescriptor diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/decoders.kt similarity index 97% rename from firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt rename to firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/decoders.kt index 3822399d2..bdac459bd 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/decoders.kt @@ -2,8 +2,9 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.DecodeSettings import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationException @@ -16,7 +17,7 @@ import kotlinx.serialization.serializer inline fun decode(value: Any?): T = decode(value) {} inline fun decode(value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T = - decode(value, DecodeSettings.BuilderImpl().apply(buildSettings).buildDecodeSettings()) + decode(value, DecodeSettingsImpl.Builder().apply(buildSettings).buildDecodeSettings()) @PublishedApi internal inline fun decode(value: Any?, decodeSettings: DecodeSettings): T { @@ -25,7 +26,7 @@ internal inline fun decode(value: Any?, decodeSettings: DecodeSettin } fun decode(strategy: DeserializationStrategy, value: Any?): T = decode(strategy, value) {} inline fun decode(strategy: DeserializationStrategy, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T = - decode(strategy, value, DecodeSettings.BuilderImpl().apply(buildSettings).buildDecodeSettings()) + decode(strategy, value, DecodeSettingsImpl.Builder().apply(buildSettings).buildDecodeSettings()) @PublishedApi internal fun decode(strategy: DeserializationStrategy, value: Any?, decodeSettings: DecodeSettings): T { @@ -37,7 +38,7 @@ expect fun getPolymorphicType(value: Any?, discriminator: String): String class FirebaseDecoder(val value: Any?, internal val settings: DecodeSettings) : Decoder { - constructor(value: Any?) : this(value, DecodeSettings()) + constructor(value: Any?) : this(value, DecodeSettingsImpl()) override val serializersModule: SerializersModule = settings.serializersModule diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt similarity index 93% rename from firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt rename to firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt index 54a3d4472..11a06bdaf 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt @@ -2,8 +2,10 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.EncodeSettings +import dev.gitlive.firebase.EncodedObject import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.SerialDescriptor @@ -11,15 +13,6 @@ import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule -/** - * Platform specific object for storing encoded data that can be used for methods that explicitly require an object. - * This is essentially a [Map] of [String] and [Any]? (as represented by [raw]) but since [encode] gives a platform specific value, this method wraps that. - * - * Created using [encodeAsObject]. It is not recommended to encode to this manually. - */ -expect interface EncodedObject { - val raw: Map -} @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(strategy, value) { encodeDefaults = shouldEncodeElementDefault }")) fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = encode(strategy, value) { @@ -27,7 +20,7 @@ fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElement } inline fun encode(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit) = - encode(strategy, value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) + encode(strategy, value, EncodeSettingsImpl.Builder().apply(buildSettings).buildEncodeSettings()) @PublishedApi internal inline fun encode(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings): Any? = @@ -39,7 +32,7 @@ inline fun encode(value: T, shouldEncodeElementDefault: Boolean): An } inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) + encode(value, EncodeSettingsImpl.Builder().apply(buildSettings).buildEncodeSettings()) /** * Encodes data as an [EncodedObject]. @@ -111,7 +104,7 @@ class FirebaseEncoder( ) : Encoder { constructor(shouldEncodeElementDefault: Boolean) : this( - EncodeSettings.BuilderImpl().apply { this.encodeDefaults = shouldEncodeElementDefault }.buildEncodeSettings() + EncodeSettingsImpl.Builder().apply { this.encodeDefaults = shouldEncodeElementDefault }.buildEncodeSettings() ) var value: Any? = null diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/reencodeTransformation.kt similarity index 90% rename from firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt rename to firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/reencodeTransformation.kt index 7c9704157..f7e08d1a1 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/reencodeTransformation.kt @@ -1,5 +1,6 @@ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder import kotlinx.serialization.KSerializer inline fun reencodeTransformation(value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit = {}, transform: (T) -> T): Any? { diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/serializers.kt similarity index 99% rename from firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt rename to firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/serializers.kt index 68e9def69..2598bdfa6 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/serializers.kt @@ -2,7 +2,7 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt similarity index 93% rename from firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt rename to firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt index 3c3c7b420..124a431e4 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt @@ -2,8 +2,11 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.nativeAssertEquals +import dev.gitlive.firebase.nativeListOf +import dev.gitlive.firebase.nativeMapOf import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer @@ -420,7 +423,10 @@ class EncodersTest { @Test fun encodeAsObject() { val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) - val encodedObject = encodeAsObject(TestData.serializer(), testDataClass) { encodeDefaults = false } + val encodedObject = encodeAsObject( + TestData.serializer(), + testDataClass + ) { encodeDefaults = false } nativeAssertEquals(mapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), encodedObject.raw) @@ -429,17 +435,45 @@ class EncodersTest { assertEquals(emptyMap(), encodeAsObject(TestObject).raw) - assertFailsWith { encodeAsObject(true) } + assertFailsWith { + encodeAsObject( + true + ) + } assertFailsWith { encodeAsObject(42) } assertFailsWith { encodeAsObject(8.toShort()) } assertFailsWith { encodeAsObject(Int.MAX_VALUE.toLong() + 3) } - assertFailsWith { encodeAsObject(0x03F) } - assertFailsWith { encodeAsObject(3.33) } - assertFailsWith { encodeAsObject(6.65f) } + assertFailsWith { + encodeAsObject( + 0x03F + ) + } + assertFailsWith { + encodeAsObject( + 3.33 + ) + } + assertFailsWith { + encodeAsObject( + 6.65f + ) + } assertFailsWith { encodeAsObject("Test") } - assertFailsWith { encodeAsObject(ValueClass(2)) } - assertFailsWith { encodeAsObject(mapOf(1 to "one")) } - assertFailsWith { encodeAsObject(listOf("one")) } + assertFailsWith { + encodeAsObject( + ValueClass(2) + ) + } + assertFailsWith { + encodeAsObject( + mapOf(1 to "one") + ) + } + assertFailsWith { + encodeAsObject( + listOf("one") + ) + } } private inline fun assertEncode(value: T) { diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt similarity index 97% rename from firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt rename to firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt index 43589a7a9..c479c5bd6 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt @@ -2,7 +2,7 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.descriptors.PolymorphicKind diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt similarity index 94% rename from firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt rename to firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt index aaa584adb..72eac41d9 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt @@ -2,17 +2,14 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.EncodedObject import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual interface EncodedObject : Map { - actual val raw: Map -} - @PublishedApi internal data class InternalEncodedObject internal constructor( override val raw: Map diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt similarity index 98% rename from firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt rename to firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt index a849dd190..167fc7f8e 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt @@ -2,7 +2,7 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt similarity index 94% rename from firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt rename to firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt index b742128cf..0c8ea424c 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt @@ -2,21 +2,18 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.EncodedObject import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.js.Json import kotlin.js.json -actual interface EncodedObject { - actual val raw: Map - val json: Json -} - @PublishedApi -internal class InternalEncodedObject internal constructor(override val raw: Map) : EncodedObject { +internal class InternalEncodedObject internal constructor(override val raw: Map) : + EncodedObject { override val json: Json get() = json(*raw.entries.map { (key, value) -> key to value }.toTypedArray()) } diff --git a/firebase-common/package.json b/firebase-common/package.json index e5826e53d..5723b5918 100644 --- a/firebase-common/package.json +++ b/firebase-common/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-common", - "version": "1.11.1", + "version": "1.12.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-common.js", "scripts": { diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/EncodedObject.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/EncodedObject.kt new file mode 100644 index 000000000..a176bcbeb --- /dev/null +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/EncodedObject.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase + +actual interface EncodedObject : Map { + actual val raw: Map +} diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt index 076f208dc..81b7690db 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt @@ -6,65 +6,38 @@ import kotlinx.serialization.modules.SerializersModule /** * Settings used to configure encoding/decoding */ -sealed class EncodeDecodeSettings { +sealed interface EncodeDecodeSettings { /** * The [SerializersModule] to use for serialization. This allows for polymorphic serialization on runtime */ - abstract val serializersModule: SerializersModule + val serializersModule: SerializersModule } /** * [EncodeDecodeSettings] used when encoding an object * @property encodeDefaults if `true` this will explicitly encode elements even if they are their default value - * @param serializersModule the [SerializersModule] to use for serialization. This allows for polymorphic serialization on runtime */ -data class EncodeSettings internal constructor( - val encodeDefaults: Boolean, - override val serializersModule: SerializersModule, -) : EncodeDecodeSettings() { +interface EncodeSettings : EncodeDecodeSettings { + + val encodeDefaults: Boolean interface Builder { var encodeDefaults: Boolean var serializersModule: SerializersModule } - - @PublishedApi - internal class BuilderImpl : Builder { - override var encodeDefaults: Boolean = true - override var serializersModule: SerializersModule = EmptySerializersModule() - } } /** * [EncodeDecodeSettings] used when decoding an object * @param serializersModule the [SerializersModule] to use for deserialization. This allows for polymorphic serialization on runtime */ -data class DecodeSettings internal constructor( - override val serializersModule: SerializersModule = EmptySerializersModule(), -) : EncodeDecodeSettings() { +interface DecodeSettings : EncodeDecodeSettings { interface Builder { var serializersModule: SerializersModule } - - @PublishedApi - internal class BuilderImpl : Builder { - override var serializersModule: SerializersModule = EmptySerializersModule() - } } interface EncodeDecodeSettingsBuilder : EncodeSettings.Builder, DecodeSettings.Builder - -@PublishedApi -internal class EncodeDecodeSettingsBuilderImpl : EncodeDecodeSettingsBuilder { - - override var encodeDefaults: Boolean = true - override var serializersModule: SerializersModule = EmptySerializersModule() -} - -@PublishedApi -internal fun EncodeSettings.Builder.buildEncodeSettings(): EncodeSettings = EncodeSettings(encodeDefaults, serializersModule) -@PublishedApi -internal fun DecodeSettings.Builder.buildDecodeSettings(): DecodeSettings = DecodeSettings(serializersModule) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodedObject.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodedObject.kt new file mode 100644 index 000000000..1e773d16e --- /dev/null +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodedObject.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase + +/** + * Platform specific object for storing encoded data that can be used for methods that explicitly require an object. + * This is essentially a [Map] of [String] and [Any]? (as represented by [raw]) but since [encode] gives a platform specific value, this method wraps that. + */ +expect interface EncodedObject { + val raw: Map +} diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/EncodedObject.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/EncodedObject.kt new file mode 100644 index 000000000..6fa97683a --- /dev/null +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/EncodedObject.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase + +actual interface EncodedObject : Map { + actual val raw: Map +} diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/EncodedObject.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/EncodedObject.kt new file mode 100644 index 000000000..864deebf3 --- /dev/null +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/EncodedObject.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase + +import kotlin.js.Json + +actual interface EncodedObject { + actual val raw: Map + val json: Json +} diff --git a/firebase-config/package.json b/firebase-config/package.json index 454740a67..e6c4057d0 100644 --- a/firebase-config/package.json +++ b/firebase-config/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-config", - "version": "1.11.1", + "version": "1.12.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-config.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.1", + "@gitlive/firebase-app": "1.12.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-crashlytics/package.json b/firebase-crashlytics/package.json index 8b0981a1a..435166e4f 100644 --- a/firebase-crashlytics/package.json +++ b/firebase-crashlytics/package.json @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.1", + "@gitlive/firebase-app": "1.12.0", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-database/build.gradle.kts b/firebase-database/build.gradle.kts index 1b92b443c..fbcb06c43 100644 --- a/firebase-database/build.gradle.kts +++ b/firebase-database/build.gradle.kts @@ -143,7 +143,8 @@ kotlin { getByName("commonMain") { dependencies { api(project(":firebase-app")) - implementation(project(":firebase-common")) + api(project(":firebase-common")) + implementation(project(":firebase-common-internal")) } } diff --git a/firebase-database/package.json b/firebase-database/package.json index 090086c4a..a2910b27a 100644 --- a/firebase-database/package.json +++ b/firebase-database/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-database", - "version": "1.11.1", + "version": "1.12.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-database.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.1", + "@gitlive/firebase-app": "1.12.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index 98b3b5a99..f5949e386 100644 --- a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -18,8 +18,8 @@ import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type import dev.gitlive.firebase.database.FirebaseDatabase.Companion.FirebaseDatabase -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.reencodeTransformation +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.reencodeTransformation import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt index c93ba746a..b9a31cdf4 100644 --- a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt +++ b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt @@ -1,8 +1,8 @@ package dev.gitlive.firebase.database -import dev.gitlive.firebase.FirebaseDecoder -import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.internal.FirebaseDecoder +import dev.gitlive.firebase.internal.FirebaseEncoder +import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index 7d46135d0..631197f2e 100644 --- a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -14,8 +14,8 @@ import dev.gitlive.firebase.database.ChildEvent.Type.ADDED import dev.gitlive.firebase.database.ChildEvent.Type.CHANGED import dev.gitlive.firebase.database.ChildEvent.Type.MOVED import dev.gitlive.firebase.database.ChildEvent.Type.REMOVED -import dev.gitlive.firebase.encode -import dev.gitlive.firebase.encodeAsObject +import dev.gitlive.firebase.internal.encode +import dev.gitlive.firebase.internal.encodeAsObject import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer diff --git a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt index 64f551dc0..1783c7432 100644 --- a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -24,8 +24,8 @@ import dev.gitlive.firebase.database.ChildEvent.Type.ADDED import dev.gitlive.firebase.database.ChildEvent.Type.CHANGED import dev.gitlive.firebase.database.ChildEvent.Type.MOVED import dev.gitlive.firebase.database.ChildEvent.Type.REMOVED -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.reencodeTransformation +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.reencodeTransformation import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index ba3e720b2..725b8c4bd 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -28,8 +28,8 @@ import dev.gitlive.firebase.database.externals.ref import dev.gitlive.firebase.database.externals.remove import dev.gitlive.firebase.database.externals.set import dev.gitlive.firebase.database.externals.update -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.reencodeTransformation +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.reencodeTransformation import kotlinx.coroutines.asDeferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.coroutineScope diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/database.kt index 52f71422b..b9390b21b 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/database.kt @@ -3,7 +3,7 @@ package dev.gitlive.firebase.database.externals -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Unsubscribe import dev.gitlive.firebase.externals.FirebaseApp import kotlin.js.Promise diff --git a/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/database.kt index 27668d277..1ddad5625 100644 --- a/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -5,7 +5,6 @@ @file:JvmName("tests") package dev.gitlive.firebase.database - actual val emulatorHost: String = "10.0.2.2" actual val context: Any = Unit diff --git a/firebase-firestore/build.gradle.kts b/firebase-firestore/build.gradle.kts index b6cf58b99..4e861b3d0 100644 --- a/firebase-firestore/build.gradle.kts +++ b/firebase-firestore/build.gradle.kts @@ -156,7 +156,8 @@ kotlin { getByName("commonMain") { dependencies { api(project(":firebase-app")) - implementation(project(":firebase-common")) + api(project(":firebase-common")) + implementation(project(":firebase-common-internal")) } } diff --git a/firebase-firestore/package.json b/firebase-firestore/package.json index eed05cf10..1a5e44bd0 100644 --- a/firebase-firestore/package.json +++ b/firebase-firestore/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-firestore", - "version": "1.11.1", + "version": "1.12.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-firestore.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.1", + "@gitlive/firebase-app": "1.12.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt index 0f2f8fe30..79951b4a3 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt @@ -1,7 +1,7 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.internal.FirebaseEncoder +import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt index 2dc95492f..5b53d2f95 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt @@ -1,7 +1,7 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.internal.FirebaseEncoder +import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt index 221456628..e96308cee 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt @@ -1,6 +1,6 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt index 92fe32f17..4d126d277 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt @@ -1,7 +1,6 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.SpecialValueSerializer -import dev.gitlive.firebase.firestore.* +import dev.gitlive.firebase.internal.SpecialValueSerializer import dev.gitlive.firebase.firestore.DoubleAsTimestampSerializer.serverTimestamp import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt index 04a3f32cf..b5f1960dd 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt @@ -11,5 +11,5 @@ internal inline fun encode(value: T, buildSettings: EncodeSettings.B if (value?.let(::isSpecialValue) == true) { value } else { - dev.gitlive.firebase.encode(value, buildSettings) + dev.gitlive.firebase.internal.encode(value, buildSettings) } diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 0aaf75146..0f1935672 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -4,7 +4,14 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeSettings +import dev.gitlive.firebase.EncodedObject +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.encodeAsObject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.serialization.DeserializationStrategy diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FieldValueTests.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FieldValueTests.kt index ad4b5374b..96c5350e8 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FieldValueTests.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FieldValueTests.kt @@ -1,6 +1,6 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.firebaseSerializer +import dev.gitlive.firebase.internal.firebaseSerializer import dev.gitlive.firebase.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/GeoPointTests.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/GeoPointTests.kt index 216621064..b97eb8378 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/GeoPointTests.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/GeoPointTests.kt @@ -1,6 +1,8 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.firebaseSerializer +import dev.gitlive.firebase.runTest import kotlinx.serialization.Serializable import kotlin.test.Test import kotlin.test.assertEquals diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/TimestampTests.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/TimestampTests.kt index 8c2541ba9..625a3f68c 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/TimestampTests.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/TimestampTests.kt @@ -1,8 +1,8 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode -import dev.gitlive.firebase.firebaseSerializer +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.encode +import dev.gitlive.firebase.internal.firebaseSerializer import dev.gitlive.firebase.nativeAssertEquals import dev.gitlive.firebase.nativeMapOf import dev.gitlive.firebase.runTest diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index f04c93853..facdfb006 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -7,11 +7,11 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps -import dev.gitlive.firebase.decode +import dev.gitlive.firebase.internal.decode import dev.gitlive.firebase.initialize import dev.gitlive.firebase.runBlockingTest import dev.gitlive.firebase.runTest -import dev.gitlive.firebase.withSerializer +import dev.gitlive.firebase.internal.withSerializer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.delay diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt index 7e54257da..409760667 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt @@ -3,7 +3,6 @@ package dev.gitlive.firebase.firestore import cocoapods.FirebaseFirestoreInternal.FIRGeoPoint import kotlinx.serialization.Serializable - /** A class representing a platform specific Firebase GeoPoint. */ actual typealias NativeGeoPoint = FIRGeoPoint diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 17f021f6a..6a391825c 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -6,7 +6,10 @@ package dev.gitlive.firebase.firestore import cocoapods.FirebaseFirestoreInternal.* import cocoapods.FirebaseFirestoreInternal.FIRDocumentChangeType.* -import dev.gitlive.firebase.* +import dev.gitlive.firebase.EncodedObject +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException import kotlinx.cinterop.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.awaitClose diff --git a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/ContextSwitchTest.kt b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/ContextSwitchTest.kt index ac4e668eb..d59d4980a 100644 --- a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/ContextSwitchTest.kt +++ b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/ContextSwitchTest.kt @@ -1,6 +1,10 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.async @@ -22,7 +26,6 @@ import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals - private val backgroundContext = newSingleThreadContext("background") /** * This function performs is intended to test object sharing across several threads. diff --git a/firebase-functions/build.gradle.kts b/firebase-functions/build.gradle.kts index 0e30f2e50..e164aee90 100644 --- a/firebase-functions/build.gradle.kts +++ b/firebase-functions/build.gradle.kts @@ -132,7 +132,8 @@ kotlin { getByName("commonMain") { dependencies { api(project(":firebase-app")) - implementation(project(":firebase-common")) + api(project(":firebase-common")) + implementation(project(":firebase-common-internal")) } } diff --git a/firebase-functions/package.json b/firebase-functions/package.json index 0e5277af7..585e2773f 100644 --- a/firebase-functions/package.json +++ b/firebase-functions/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-functions", - "version": "1.11.1", + "version": "1.12.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-functions.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.1", + "@gitlive/firebase-app": "1.12.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-functions/src/androidMain/kotlin/dev/gitlive/firebase/functions/functions.kt b/firebase-functions/src/androidMain/kotlin/dev/gitlive/firebase/functions/functions.kt index d93f743ec..e4a72b95d 100644 --- a/firebase-functions/src/androidMain/kotlin/dev/gitlive/firebase/functions/functions.kt +++ b/firebase-functions/src/androidMain/kotlin/dev/gitlive/firebase/functions/functions.kt @@ -7,7 +7,7 @@ package dev.gitlive.firebase.functions import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.decode +import dev.gitlive.firebase.internal.decode import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy import java.util.concurrent.TimeUnit diff --git a/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt b/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt index 9a152a430..24a999463 100644 --- a/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt +++ b/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt @@ -4,7 +4,12 @@ package dev.gitlive.firebase.functions -import dev.gitlive.firebase.* +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeSettings +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.internal.encode import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy diff --git a/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt b/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt index 907508278..04e0b608a 100644 --- a/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt +++ b/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt @@ -7,10 +7,13 @@ package dev.gitlive.firebase.functions import cocoapods.FirebaseFunctions.FIRFunctions import cocoapods.FirebaseFunctions.FIRHTTPSCallable import cocoapods.FirebaseFunctions.FIRHTTPSCallableResult -import dev.gitlive.firebase.* +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.internal.decode import kotlinx.coroutines.CompletableDeferred import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerializationStrategy import platform.Foundation.NSError actual val Firebase.functions diff --git a/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/functions.kt b/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/functions.kt index b8b9b7bee..cc6f8f100 100644 --- a/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/functions.kt +++ b/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/functions.kt @@ -4,11 +4,19 @@ package dev.gitlive.firebase.functions -import dev.gitlive.firebase.* -import dev.gitlive.firebase.functions.externals.* +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.functions.externals.Functions +import dev.gitlive.firebase.functions.externals.HttpsCallable +import dev.gitlive.firebase.functions.externals.connectFunctionsEmulator +import dev.gitlive.firebase.functions.externals.getFunctions +import dev.gitlive.firebase.functions.externals.httpsCallable +import dev.gitlive.firebase.functions.externals.invoke +import dev.gitlive.firebase.internal.decode import kotlinx.coroutines.await import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerializationStrategy import kotlin.js.json import dev.gitlive.firebase.functions.externals.HttpsCallableResult as JsHttpsCallableResult diff --git a/firebase-installations/package.json b/firebase-installations/package.json index ab86a6f71..d4b77bb64 100644 --- a/firebase-installations/package.json +++ b/firebase-installations/package.json @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.1", + "@gitlive/firebase-app": "1.12.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/installations.kt b/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/installations.kt index 7329c3626..b1ddce4e0 100644 --- a/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/installations.kt +++ b/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/installations.kt @@ -1,6 +1,8 @@ package dev.gitlive.firebase.installations -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.installations.externals.* import kotlinx.coroutines.await diff --git a/firebase-perf/package.json b/firebase-perf/package.json index f454d775c..c51f39a2c 100644 --- a/firebase-perf/package.json +++ b/firebase-perf/package.json @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.1", + "@gitlive/firebase-app": "1.12.0", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index 6768f9247..894b90f25 100644 --- a/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -1,6 +1,9 @@ package dev.gitlive.firebase.perf.metrics -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize import dev.gitlive.firebase.perf.FirebasePerformance import dev.gitlive.firebase.perf.context import dev.gitlive.firebase.perf.performance diff --git a/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index 65ccafb65..252bf751f 100644 --- a/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -1,6 +1,9 @@ package dev.gitlive.firebase.perf.metrics -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize import dev.gitlive.firebase.perf.FirebasePerformance import dev.gitlive.firebase.perf.IgnoreForAndroidUnitTest import dev.gitlive.firebase.perf.context diff --git a/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/performance.kt index fae090c5c..6ca689abc 100644 --- a/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -4,10 +4,13 @@ package dev.gitlive.firebase.perf -import dev.gitlive.firebase.* -import kotlinx.coroutines.CoroutineScope +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest +import dev.gitlive.firebase.runTest import kotlinx.coroutines.delay -import kotlinx.coroutines.test.TestResult import kotlin.test.* import kotlin.time.Duration.Companion.seconds diff --git a/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index c732a4a82..0b7499ed0 100644 --- a/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -1,10 +1,14 @@ package dev.gitlive.firebase.perf.metrics -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize import dev.gitlive.firebase.perf.FirebasePerformance import dev.gitlive.firebase.perf.context import dev.gitlive.firebase.perf.performance import dev.gitlive.firebase.runBlockingTest +import dev.gitlive.firebase.runTest import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test diff --git a/firebase-storage/package.json b/firebase-storage/package.json index 67f721da8..db6ab752b 100644 --- a/firebase-storage/package.json +++ b/firebase-storage/package.json @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.1", + "@gitlive/firebase-app": "1.12.0", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/gradle.properties b/gradle.properties index 3fb471e2b..e8d09666a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,6 +25,7 @@ firebase-app.skipIosTests=false # We are skipping auth ios tests due to an issue with keychain and simulator. firebase-auth.skipIosTests=true firebase-common.skipIosTests=false +firebase-common-internal.skipIosTests=false firebase-config.skipIosTests=false firebase-database.skipIosTests=false firebase-firestore.skipIosTests=false @@ -38,6 +39,7 @@ firebase-storage.skipIosTests=false firebase-app.skipJsTests=false firebase-auth.skipJsTests=false firebase-common.skipJsTests=false +firebase-common-internal.skipJsTests=false firebase-config.skipJsTests=false firebase-database.skipJsTests=false firebase-firestore.skipJsTests=false @@ -50,6 +52,7 @@ firebase-storage.skipJsTests=false firebase-app.version=1.12.0 firebase-auth.version=1.12.0 firebase-common.version=1.12.0 +firebase-common-internal.version=1.12.0 firebase-config.version=1.12.0 firebase-database.version=1.12.0 firebase-firestore.version=1.12.0 diff --git a/settings.gradle.kts b/settings.gradle.kts index dd80bfd3a..68af478a5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,6 +2,7 @@ include( "firebase-app", "firebase-auth", "firebase-common", + "firebase-common-internal", "firebase-config", "firebase-database", "firebase-firestore", @@ -20,5 +21,7 @@ pluginManagement { kotlin("multiplatform") version kotlinVersion kotlin("native.cocoapods") version kotlinVersion kotlin("plugin.serialization") version kotlinVersion + id("com.android.application") version "8.1.3" + id("org.jetbrains.kotlin.android") version "1.9.0" } } From a629f9a38d52af285c5235af68ed942e5f04514e Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sun, 14 Apr 2024 22:53:56 +0200 Subject: [PATCH 10/12] moved EncodedData to internal --- .../firebase/internal/EncodedObject.kt | 7 ++++ .../gitlive/firebase/internal/_encoders.kt | 10 ----- .../firebase/internal/EncodedObject.kt | 27 ++++++++++++++ .../dev/gitlive/firebase/internal/encoders.kt | 16 -------- .../firebase/internal/EncodedObject.kt | 6 +++ .../gitlive/firebase/internal/_encoders.kt | 12 ------ .../firebase/internal/EncodedObject.kt | 32 ++++++++++++++++ .../gitlive/firebase/internal/_encoders.kt | 37 ------------------- .../dev/gitlive/firebase/EncodedObject.kt | 9 ----- .../dev/gitlive/firebase/EncodedObject.kt | 13 ------- .../dev/gitlive/firebase/EncodedObject.kt | 9 ----- .../dev/gitlive/firebase/EncodedObject.kt | 12 ------ .../dev/gitlive/firebase/database/database.kt | 7 ++-- .../dev/gitlive/firebase/database/database.kt | 2 +- .../dev/gitlive/firebase/database/database.kt | 7 ++-- .../dev/gitlive/firebase/database/database.kt | 7 ++-- .../gitlive/firebase/firestore/firestore.kt | 23 ++++++------ .../gitlive/firebase/firestore/firestore.kt | 2 +- .../gitlive/firebase/firestore/firestore.kt | 35 +++++++++--------- .../gitlive/firebase/firestore/firestore.kt | 19 +++++----- 20 files changed, 126 insertions(+), 166 deletions(-) create mode 100644 firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt create mode 100644 firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt create mode 100644 firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt create mode 100644 firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt delete mode 100644 firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/EncodedObject.kt delete mode 100644 firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodedObject.kt delete mode 100644 firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/EncodedObject.kt delete mode 100644 firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/EncodedObject.kt diff --git a/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt new file mode 100644 index 000000000..6d7f8d63a --- /dev/null +++ b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -0,0 +1,7 @@ +@file:JvmName("AndroidEncodedObject") +package dev.gitlive.firebase.internal + +val EncodedObject.android: Map get() = raw + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> diff --git a/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt index 0a8ce569b..d4f8316fc 100644 --- a/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt +++ b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt @@ -4,21 +4,11 @@ package dev.gitlive.firebase.internal -import dev.gitlive.firebase.EncodedObject import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -@PublishedApi -internal data class EncodedObjectImpl internal constructor(override val raw: Map) : EncodedObject, Map by raw - -@PublishedApi -internal actual fun Map.asEncodedObject(): EncodedObject = EncodedObjectImpl(this) - -@PublishedApi -internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> - actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> mutableListOf() .also { value = it } diff --git a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt new file mode 100644 index 000000000..e02664500 --- /dev/null +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -0,0 +1,27 @@ +package dev.gitlive.firebase.internal + +import kotlin.jvm.JvmInline + +/** + * Platform specific object for storing encoded data that can be used for methods that explicitly require an object. + * This is essentially a [Map] of [String] and [Any]? (as represented by [raw]) but since [encode] gives a platform specific value, this method wraps that. + */ +sealed interface EncodedObject { + val raw: Map +} + +@JvmInline +@PublishedApi +internal value class EncodedObjectImpl(override val raw: Map) : EncodedObject + +@PublishedApi +internal expect fun Any.asNativeMap(): Map<*, *>? + +@PublishedApi +internal fun Map<*, *>.asEncodedObject(): EncodedObject = map { (key, value) -> + if (key is String) { + key to value + } else { + throw IllegalArgumentException("Expected a String key but received $key") + } +}.toMap().let(::EncodedObjectImpl) diff --git a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt index 11a06bdaf..0015e3cfd 100644 --- a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt @@ -5,7 +5,6 @@ package dev.gitlive.firebase.internal import dev.gitlive.firebase.EncodeSettings -import dev.gitlive.firebase.EncodedObject import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.SerialDescriptor @@ -74,21 +73,6 @@ internal inline fun encode(value: T, encodeSettings: EncodeSettings) }.value } -@PublishedApi -expect internal fun Any.asNativeMap(): Map<*, *>? - -@PublishedApi -internal fun Map<*, *>.asEncodedObject(): EncodedObject = map { (key, value) -> - if (key is String) { - key to value - } else { - throw IllegalArgumentException("Expected a String key but received $key") - } -}.toMap().asEncodedObject() - -@PublishedApi -internal expect fun Map.asEncodedObject(): EncodedObject - /** * An extension which which serializer to use for value. Handy in updating fields by name or path * where using annotation is not possible diff --git a/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt new file mode 100644 index 000000000..c261934f1 --- /dev/null +++ b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -0,0 +1,6 @@ +package dev.gitlive.firebase.internal + +val EncodedObject.ios: Map get() = raw.mapKeys { (key, _) -> key } + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> diff --git a/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt index 72eac41d9..966a7e64e 100644 --- a/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt +++ b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt @@ -4,23 +4,11 @@ package dev.gitlive.firebase.internal -import dev.gitlive.firebase.EncodedObject import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -@PublishedApi -internal data class InternalEncodedObject internal constructor( - override val raw: Map -) : EncodedObject, Map by raw.mapKeys({ (key, _) -> key }) - -@PublishedApi -internal actual fun Map.asEncodedObject(): EncodedObject = InternalEncodedObject(this) - -@PublishedApi -internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> - actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> encodeAsList() StructureKind.MAP -> mutableListOf() diff --git a/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt new file mode 100644 index 000000000..5fc84a829 --- /dev/null +++ b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -0,0 +1,32 @@ +package dev.gitlive.firebase.internal + +import kotlin.js.Json +import kotlin.js.json + +val EncodedObject.js: Json get() = json(*raw.entries.map { (key, value) -> key to value }.toTypedArray()) + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? { + val json = when (this) { + is Number -> null + is Boolean -> null + is String -> null + is Map<*, *> -> { + if (keys.all { it is String }) { + this as Json + } else { + null + } + } + is Collection<*> -> null + is Array<*> -> null + else -> { + this as Json + } + } ?: return null + val mutableMap = mutableMapOf() + for (key in js("Object").keys(json)) { + mutableMap[key] = json[key] + } + return mutableMap.toMap() +} diff --git a/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt index 0c8ea424c..5cf22b6bd 100644 --- a/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt +++ b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt @@ -4,48 +4,11 @@ package dev.gitlive.firebase.internal -import dev.gitlive.firebase.EncodedObject import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind -import kotlin.js.Json import kotlin.js.json -@PublishedApi -internal class InternalEncodedObject internal constructor(override val raw: Map) : - EncodedObject { - override val json: Json get() = json(*raw.entries.map { (key, value) -> key to value }.toTypedArray()) -} - -@PublishedApi -internal actual fun Map.asEncodedObject(): EncodedObject = InternalEncodedObject(this) - -@PublishedApi -internal actual fun Any.asNativeMap(): Map<*, *>? { - val json = when (this) { - is Number -> null - is Boolean -> null - is String -> null - is Map<*, *> -> { - if (keys.all { it is String }) { - this as Json - } else { - null - } - } - is Collection<*> -> null - is Array<*> -> null - else -> { - this as Json - } - } ?: return null - val mutableMap = mutableMapOf() - for (key in js("Object").keys(json)) { - mutableMap[key] = json[key] - } - return mutableMap.toMap() -} - actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> encodeAsList(descriptor) StructureKind.MAP -> { diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/EncodedObject.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/EncodedObject.kt deleted file mode 100644 index a176bcbeb..000000000 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/EncodedObject.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.gitlive.firebase - -actual interface EncodedObject : Map { - actual val raw: Map -} diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodedObject.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodedObject.kt deleted file mode 100644 index 1e773d16e..000000000 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodedObject.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.gitlive.firebase - -/** - * Platform specific object for storing encoded data that can be used for methods that explicitly require an object. - * This is essentially a [Map] of [String] and [Any]? (as represented by [raw]) but since [encode] gives a platform specific value, this method wraps that. - */ -expect interface EncodedObject { - val raw: Map -} diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/EncodedObject.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/EncodedObject.kt deleted file mode 100644 index 6fa97683a..000000000 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/EncodedObject.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.gitlive.firebase - -actual interface EncodedObject : Map { - actual val raw: Map -} diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/EncodedObject.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/EncodedObject.kt deleted file mode 100644 index 864deebf3..000000000 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/EncodedObject.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.gitlive.firebase - -import kotlin.js.Json - -actual interface EncodedObject { - actual val raw: Map - val json: Json -} diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index f5949e386..0e14cb8fb 100644 --- a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -13,11 +13,12 @@ import com.google.firebase.database.Transaction import com.google.firebase.database.ValueEventListener import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeDecodeSettingsBuilder -import dev.gitlive.firebase.EncodedObject +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type import dev.gitlive.firebase.database.FirebaseDatabase.Companion.FirebaseDatabase +import dev.gitlive.firebase.internal.android import dev.gitlive.firebase.internal.decode import dev.gitlive.firebase.internal.reencodeTransformation import kotlinx.coroutines.CompletableDeferred @@ -207,7 +208,7 @@ internal actual class NativeDatabaseReference internal constructor( .run { Unit } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - android.updateChildren(encodedUpdate) + android.updateChildren(encodedUpdate.android) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @@ -297,7 +298,7 @@ internal actual class NativeOnDisconnect internal constructor( .run { Unit } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - android.updateChildren(encodedUpdate) + android.updateChildren(encodedUpdate.android) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } } diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index 631197f2e..55df2a2e5 100644 --- a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -7,7 +7,7 @@ package dev.gitlive.firebase.database import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeDecodeSettingsBuilder import dev.gitlive.firebase.EncodeSettings -import dev.gitlive.firebase.EncodedObject +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type.ADDED diff --git a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt index 1783c7432..45fad1aa4 100644 --- a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -16,7 +16,6 @@ import cocoapods.FirebaseDatabase.FIRDatabaseReference import cocoapods.FirebaseDatabase.FIRTransactionResult import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeDecodeSettingsBuilder -import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type @@ -24,7 +23,9 @@ import dev.gitlive.firebase.database.ChildEvent.Type.ADDED import dev.gitlive.firebase.database.ChildEvent.Type.CHANGED import dev.gitlive.firebase.database.ChildEvent.Type.MOVED import dev.gitlive.firebase.database.ChildEvent.Type.REMOVED +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.ios import dev.gitlive.firebase.internal.reencodeTransformation import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.FlowPreview @@ -170,7 +171,7 @@ internal actual class NativeDatabaseReference internal constructor( } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { - ios.await(persistenceEnabled) { updateChildValues(encodedUpdate, it) } + ios.await(persistenceEnabled) { updateChildValues(encodedUpdate.ios, it) } } actual suspend fun removeValue() { @@ -241,7 +242,7 @@ internal actual class NativeOnDisconnect internal constructor( } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { - ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate, it) } + ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate.ios, it) } } } diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index 725b8c4bd..c5aeb4941 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -6,7 +6,6 @@ package dev.gitlive.firebase.database import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeDecodeSettingsBuilder -import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.externals.CancelCallback @@ -28,7 +27,9 @@ import dev.gitlive.firebase.database.externals.ref import dev.gitlive.firebase.database.externals.remove import dev.gitlive.firebase.database.externals.set import dev.gitlive.firebase.database.externals.update +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.js import dev.gitlive.firebase.internal.reencodeTransformation import kotlinx.coroutines.asDeferred import kotlinx.coroutines.channels.awaitClose @@ -183,7 +184,7 @@ internal actual class NativeDatabaseReference internal constructor( } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { update(js, encodedUpdate.json).awaitWhileOnline(database) } + rethrow { update(js, encodedUpdate.js).awaitWhileOnline(database) } actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { @@ -235,7 +236,7 @@ internal actual class NativeOnDisconnect internal constructor( rethrow { js.set(encodedValue).awaitWhileOnline(database) } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { js.update(encodedUpdate.json).awaitWhileOnline(database) } + rethrow { js.update(encodedUpdate.js).awaitWhileOnline(database) } } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index a035a9957..e45037420 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -6,9 +6,10 @@ package dev.gitlive.firebase.firestore import com.google.firebase.firestore.MetadataChanges -import dev.gitlive.firebase.EncodedObject +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.internal.android import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -84,12 +85,12 @@ internal actual class NativeWriteBatchWrapper actual internal constructor(actual encodedData: EncodedObject, setOptions: SetOptions ): NativeWriteBatchWrapper = (setOptions.android?.let { - native.set(documentRef.android, encodedData, it) - } ?: native.set(documentRef.android, encodedData)).let { + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android)).let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, @@ -126,12 +127,12 @@ internal actual class NativeTransactionWrapper actual internal constructor(actua setOptions: SetOptions ): NativeTransactionWrapper { setOptions.android?.let { - native.set(documentRef.android, encodedData, it) - } ?: native.set(documentRef.android, encodedData) + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android) return this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, @@ -178,13 +179,13 @@ internal actual class NativeDocumentReference actual constructor(actual val nati actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) { val task = (setOptions.android?.let { - android.set(encodedData, it) - } ?: android.set(encodedData)) + android.set(encodedData.android, it) + } ?: android.set(encodedData.android)) task.await() } actual suspend fun updateEncoded(encodedData: EncodedObject) { - android.update(encodedData).await() + android.update(encodedData.android).await() } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { @@ -350,7 +351,7 @@ internal actual class NativeCollectionReferenceWrapper internal actual construct actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(native.add(data).await()) + actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(native.add(data.android).await()) } val CollectionReference.android get() = native diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 0f1935672..5923e53d5 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -6,7 +6,7 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeSettings -import dev.gitlive.firebase.EncodedObject +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 6a391825c..b2c676ecf 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -6,10 +6,11 @@ package dev.gitlive.firebase.firestore import cocoapods.FirebaseFirestoreInternal.* import cocoapods.FirebaseFirestoreInternal.FIRDocumentChangeType.* -import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios import kotlinx.cinterop.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.awaitClose @@ -80,13 +81,13 @@ internal actual class NativeWriteBatchWrapper actual constructor(actual val nati encodedData: EncodedObject, setOptions: SetOptions ): NativeWriteBatchWrapper = when (setOptions) { - is SetOptions.Merge -> native.setData(encodedData, documentRef.ios, true) - is SetOptions.Overwrite -> native.setData(encodedData, documentRef.ios, false) - is SetOptions.MergeFields -> native.setData(encodedData, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> native.setData(encodedData, documentRef.ios, setOptions.encodedFieldPaths) + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData, documentRef.ios).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, @@ -122,13 +123,13 @@ internal actual class NativeTransactionWrapper actual constructor(actual val nat encodedData: EncodedObject, setOptions: SetOptions ): NativeTransactionWrapper = when (setOptions) { - is SetOptions.Merge -> native.setData(encodedData, documentRef.ios, true) - is SetOptions.Overwrite -> native.setData(encodedData, documentRef.ios, false) - is SetOptions.MergeFields -> native.setData(encodedData, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> native.setData(encodedData, documentRef.ios, setOptions.encodedFieldPaths) + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData, documentRef.ios).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, @@ -189,15 +190,15 @@ internal actual class NativeDocumentReference actual constructor(actual val nati actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = await { when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData, true, it) - is SetOptions.Overwrite -> ios.setData(encodedData, false, it) - is SetOptions.MergeFields -> ios.setData(encodedData, setOptions.fields, it) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData, setOptions.encodedFieldPaths, it) + is SetOptions.Merge -> ios.setData(encodedData.ios, true, it) + is SetOptions.Overwrite -> ios.setData(encodedData.ios, false, it) + is SetOptions.MergeFields -> ios.setData(encodedData.ios, setOptions.fields, it) + is SetOptions.MergeFieldPaths -> ios.setData(encodedData.ios, setOptions.encodedFieldPaths, it) } } actual suspend fun updateEncoded(encodedData: EncodedObject) = await { - ios.updateData(encodedData, it) + ios.updateData(encodedData.ios, it) } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = await { @@ -313,7 +314,7 @@ internal actual class NativeCollectionReferenceWrapper internal actual construct actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { native.addDocumentWithData(data, it) }) + actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { native.addDocumentWithData(data.ios, it) }) } val CollectionReference.ios get() = native diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index c6b3effff..8161313f3 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -4,7 +4,6 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException @@ -16,7 +15,6 @@ import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator import dev.gitlive.firebase.firestore.externals.deleteDoc import dev.gitlive.firebase.firestore.externals.doc -import dev.gitlive.firebase.firestore.externals.documentId as jsDocumentId import dev.gitlive.firebase.firestore.externals.enableIndexedDbPersistence import dev.gitlive.firebase.firestore.externals.getDoc import dev.gitlive.firebase.firestore.externals.getDocs @@ -30,6 +28,8 @@ import dev.gitlive.firebase.firestore.externals.refEqual import dev.gitlive.firebase.firestore.externals.setDoc import dev.gitlive.firebase.firestore.externals.setLogLevel import dev.gitlive.firebase.firestore.externals.writeBatch +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.await import kotlinx.coroutines.channels.awaitClose @@ -51,6 +51,7 @@ import dev.gitlive.firebase.firestore.externals.WriteBatch as JsWriteBatch import dev.gitlive.firebase.firestore.externals.collection as jsCollection import dev.gitlive.firebase.firestore.externals.collectionGroup as jsCollectionGroup import dev.gitlive.firebase.firestore.externals.disableNetwork as jsDisableNetwork +import dev.gitlive.firebase.firestore.externals.documentId as jsDocumentId import dev.gitlive.firebase.firestore.externals.enableNetwork as jsEnableNetwork import dev.gitlive.firebase.firestore.externals.endAt as jsEndAt import dev.gitlive.firebase.firestore.externals.endBefore as jsEndBefore @@ -131,9 +132,9 @@ internal actual class NativeWriteBatchWrapper actual internal constructor(actual documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions - ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.json, setOptions.js) }.let { this } + ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.js, setOptions.js) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.json) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.js) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -177,11 +178,11 @@ internal actual class NativeTransactionWrapper actual internal constructor(actua encodedData: EncodedObject, setOptions: SetOptions ): NativeTransactionWrapper = rethrow { - js.set(documentRef.js, encodedData.json, setOptions.js) + js.set(documentRef.js, encodedData.js, setOptions.js) } .let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.json) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.js) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -245,10 +246,10 @@ internal actual class NativeDocumentReference actual constructor(actual val nati } actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { - setDoc(js, encodedData.json, setOptions.js).await() + setDoc(js, encodedData.js, setOptions.js).await() } - actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData.json).await() } + actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData.js).await() } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { rethrow { @@ -398,7 +399,7 @@ internal actual class NativeCollectionReferenceWrapper actual internal construct actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } actual suspend fun addEncoded(data: EncodedObject) = rethrow { - NativeDocumentReference(addDoc(js, data.json).await()) + NativeDocumentReference(addDoc(js, data.js).await()) } } From 83f0192f1f318fd7b11bca72923ed193e03b4e9e Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 15 Apr 2024 17:39:42 +0200 Subject: [PATCH 11/12] Make raw internal/Disable Android DB test --- .../kotlin/dev/gitlive/firebase/internal/EncodedObject.kt | 2 +- .../kotlin/dev/gitlive/firebase/internal/EncodedObject.kt | 8 +++++--- .../kotlin/dev/gitlive/firebase/internal/EncodersTest.kt | 6 +++--- .../kotlin/dev/gitlive/firebase/internal/EncodedObject.kt | 2 +- .../kotlin/dev/gitlive/firebase/internal/EncodedObject.kt | 2 +- .../kotlin/dev/gitlive/firebase/database/database.kt | 3 +++ .../kotlin/dev/gitlive/firebase/database/database.kt | 1 + .../kotlin/dev/gitlive/firebase/database/database.kt | 3 +++ .../kotlin/dev/gitlive/firebase/database/database.kt | 2 ++ .../kotlin/dev/gitlive/firebase/database/database.kt | 2 ++ gradle.properties | 2 +- 11 files changed, 23 insertions(+), 10 deletions(-) diff --git a/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt index 6d7f8d63a..1e5fc20a8 100644 --- a/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt +++ b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -1,7 +1,7 @@ @file:JvmName("AndroidEncodedObject") package dev.gitlive.firebase.internal -val EncodedObject.android: Map get() = raw +val EncodedObject.android: Map get() = getRaw() @PublishedApi internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> diff --git a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt index e02664500..9c070b82d 100644 --- a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -6,13 +6,15 @@ import kotlin.jvm.JvmInline * Platform specific object for storing encoded data that can be used for methods that explicitly require an object. * This is essentially a [Map] of [String] and [Any]? (as represented by [raw]) but since [encode] gives a platform specific value, this method wraps that. */ -sealed interface EncodedObject { - val raw: Map +sealed interface EncodedObject + +internal fun EncodedObject.getRaw(): Map = when (this) { + is EncodedObjectImpl -> raw } @JvmInline @PublishedApi -internal value class EncodedObjectImpl(override val raw: Map) : EncodedObject +internal value class EncodedObjectImpl(val raw: Map) : EncodedObject @PublishedApi internal expect fun Any.asNativeMap(): Map<*, *>? diff --git a/firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt b/firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt index 124a431e4..1e2385846 100644 --- a/firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt +++ b/firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt @@ -428,12 +428,12 @@ class EncodersTest { testDataClass ) { encodeDefaults = false } - nativeAssertEquals(mapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), encodedObject.raw) + nativeAssertEquals(mapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), encodedObject.getRaw()) val testMap = mapOf("one" to 1, "two" to null, "three" to false) - assertEquals(testMap, encodeAsObject(testMap).raw) + assertEquals(testMap, encodeAsObject(testMap).getRaw()) - assertEquals(emptyMap(), encodeAsObject(TestObject).raw) + assertEquals(emptyMap(), encodeAsObject(TestObject).getRaw()) assertFailsWith { encodeAsObject( diff --git a/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt index c261934f1..9d4e6643a 100644 --- a/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt +++ b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -1,6 +1,6 @@ package dev.gitlive.firebase.internal -val EncodedObject.ios: Map get() = raw.mapKeys { (key, _) -> key } +val EncodedObject.ios: Map get() = getRaw().mapKeys { (key, _) -> key } @PublishedApi internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> diff --git a/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt index 5fc84a829..47f3aad5e 100644 --- a/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt +++ b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -3,7 +3,7 @@ package dev.gitlive.firebase.internal import kotlin.js.Json import kotlin.js.json -val EncodedObject.js: Json get() = json(*raw.entries.map { (key, value) -> key to value }.toTypedArray()) +val EncodedObject.js: Json get() = json(*getRaw().entries.map { (key, value) -> key to value }.toTypedArray()) @PublishedApi internal actual fun Any.asNativeMap(): Map<*, *>? { diff --git a/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/database.kt index d0b8ea390..c10dd8972 100644 --- a/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -6,9 +6,12 @@ package dev.gitlive.firebase.database import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Ignore actual val emulatorHost: String = "10.0.2.2" actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest + +actual typealias IgnoreForAndroidTest = Ignore diff --git a/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/database.kt index b0fbae1d3..a54a41bbd 100644 --- a/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -11,3 +11,4 @@ actual val emulatorHost: String = "10.0.2.2" actual val context: Any = "" actual typealias IgnoreForAndroidUnitTest = Ignore +actual typealias IgnoreForAndroidTest = Ignore diff --git a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt index 568d0868d..4c1c9a754 100644 --- a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -25,6 +25,7 @@ import kotlin.time.Duration.Companion.minutes expect val emulatorHost: String expect val context: Any expect annotation class IgnoreForAndroidUnitTest() +expect annotation class IgnoreForAndroidTest() @IgnoreForAndroidUnitTest class FirebaseDatabaseTest { @@ -197,6 +198,8 @@ class FirebaseDatabaseTest { assertFalse(valueEvents.first().exists) } + // Ignoring on Android Instrumented Tests due to bug in Firebase: https://github.com/firebase/firebase-android-sdk/issues/5870 + @IgnoreForAndroidTest @Test fun testUpdateChildrenOnDisconnect() = runTest { setupRealtimeData() diff --git a/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/database.kt index 410cf18bb..294fe1913 100644 --- a/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -10,3 +10,5 @@ actual val context: Any = Unit @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidTest diff --git a/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/database.kt index 48e4649bb..f1821d3c3 100644 --- a/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -6,3 +6,5 @@ actual val context: Any = Unit @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidTest diff --git a/gradle.properties b/gradle.properties index e8d09666a..adc37e244 100644 --- a/gradle.properties +++ b/gradle.properties @@ -67,6 +67,6 @@ gradlePluginVersion=8.1.3 kotlinVersion=1.9.21 coroutinesVersion=1.7.3 serializationVersion=1.6.0 -firebaseBoMVersion=32.7.0 +firebaseBoMVersion=32.8.1 apiVersion=1.8 languageVersion=1.9 From cce8b826568317b1e486bdae398d8ecedd4a3b79 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sun, 21 Apr 2024 14:12:01 +0200 Subject: [PATCH 12/12] Moved Firestore internals to different package --- .../gitlive/firebase/firestore/firestore.kt | 405 +--------------- .../NativeCollectionReferenceWrapper.kt | 25 + .../internal/NativeDocumentReference.kt | 88 ++++ .../internal/NativeDocumentSnapshotWrapper.kt | 29 ++ .../NativeFirebaseFirestoreWrapper.kt | 101 ++++ .../firestore/internal/NativeQueryWrapper.kt | 138 ++++++ .../internal/NativeTransactionWrapper.kt | 46 ++ .../internal/NativeWriteBatchWrapper.kt | 47 ++ .../firebase/firestore/internal/SetOptions.kt | 8 + .../firebase/firestore/internal/Source.kt | 10 + .../firestore/internal/callbackExecutorMap.kt | 8 + .../firestore/DocumentReferenceSerializer.kt | 1 + .../dev/gitlive/firebase/firestore/Filter.kt | 2 + .../gitlive/firebase/firestore/firestore.kt | 141 +----- .../NativeCollectionReferenceWrapper.kt | 18 + .../internal/NativeDocumentReference.kt | 27 ++ .../internal/NativeDocumentSnapshotWrapper.kt | 24 + .../NativeFirebaseFirestoreWrapper.kt | 24 + .../firestore/internal/NativeQueryWrapper.kt | 36 ++ .../internal/NativeTransactionWrapper.kt | 19 + .../internal/NativeWriteBatchWrapper.kt | 17 + .../firebase/firestore/internal/SafeValue.kt | 14 + .../firebase/firestore/internal/SetOptions.kt | 13 + .../gitlive/firebase/firestore/firestore.kt | 331 +------------- .../NativeCollectionReferenceWrapper.kt | 23 + .../internal/NativeDocumentReference.kt | 85 ++++ .../internal/NativeDocumentSnapshotWrapper.kt | 42 ++ .../NativeFirebaseFirestoreWrapper.kt | 63 +++ .../firestore/internal/NativeQueryWrapper.kt | 86 ++++ .../internal/NativeTransactionWrapper.kt | 48 ++ .../internal/NativeWriteBatchWrapper.kt | 47 ++ .../firebase/firestore/internal/Source.kt | 10 + .../firebase/firestore/internal/throwError.kt | 23 + .../gitlive/firebase/firestore/firestore.kt | 432 +----------------- .../firebase/firestore/internal/JSQueryGet.kt | 7 + .../NativeCollectionReferenceWrapper.kt | 38 ++ .../internal/NativeDocumentReference.kt | 110 +++++ .../internal/NativeDocumentSnapshotWrapper.kt | 40 ++ .../NativeFirebaseFirestoreWrapper.kt | 113 +++++ .../firestore/internal/NativeQueryWrapper.kt | 154 +++++++ .../internal/NativeTransactionWrapper.kt | 57 +++ .../internal/NativeWriteBatchWrapper.kt | 53 +++ .../firebase/firestore/internal/SetOptions.kt | 12 + 43 files changed, 1720 insertions(+), 1295 deletions(-) create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/JSQueryGet.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index c1bfeedde..e190cd5a6 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -6,28 +6,11 @@ package dev.gitlive.firebase.firestore import com.google.android.gms.tasks.TaskExecutors -import com.google.firebase.firestore.MemoryCacheSettings -import com.google.firebase.firestore.MemoryEagerGcSettings -import com.google.firebase.firestore.MemoryLruGcSettings -import com.google.firebase.firestore.MetadataChanges -import dev.gitlive.firebase.internal.EncodedObject -import com.google.firebase.firestore.PersistentCacheSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import kotlinx.coroutines.channels.ProducerScope -import dev.gitlive.firebase.firestore.Source.* -import dev.gitlive.firebase.internal.android -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.tasks.await -import java.util.concurrent.ConcurrentHashMap +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper import java.util.concurrent.Executor -import com.google.firebase.firestore.FieldPath as AndroidFieldPath -import com.google.firebase.firestore.Filter as AndroidFilter import com.google.firebase.firestore.Query as AndroidQuery -import com.google.firebase.firestore.firestoreSettings as androidFirestoreSettings import com.google.firebase.firestore.memoryCacheSettings as androidMemoryCacheSettings import com.google.firebase.firestore.memoryEagerGcSettings as androidMemoryEagerGcSettings import com.google.firebase.firestore.memoryLruGcSettings as androidMemoryLruGcSettings @@ -55,79 +38,7 @@ val LocalCacheSettings.android: com.google.firebase.firestore.LocalCacheSettings } } -// Since on iOS Callback threads are set as settings, we store the settings explicitly here as well -private val callbackExecutorMap = ConcurrentHashMap() - actual typealias NativeFirebaseFirestore = com.google.firebase.firestore.FirebaseFirestore -internal actual class NativeFirebaseFirestoreWrapper actual constructor(actual val native: NativeFirebaseFirestore) { - - actual var settings: FirebaseFirestoreSettings - get() = with(native.firestoreSettings) { - FirebaseFirestoreSettings( - isSslEnabled, - host, - cacheSettings?.let { localCacheSettings -> - when (localCacheSettings) { - is MemoryCacheSettings -> { - val garbageCollectionSettings = when (val settings = localCacheSettings.garbageCollectorSettings) { - is MemoryEagerGcSettings -> MemoryGarbageCollectorSettings.Eager - is MemoryLruGcSettings -> MemoryGarbageCollectorSettings.LRUGC(settings.sizeBytes) - else -> throw IllegalArgumentException("Existing settings does not have valid GarbageCollectionSettings") - } - LocalCacheSettings.Memory(garbageCollectionSettings) - } - - is PersistentCacheSettings -> LocalCacheSettings.Persistent(localCacheSettings.sizeBytes) - else -> throw IllegalArgumentException("Existing settings is not of a valid type") - } - } ?: kotlin.run { - @Suppress("DEPRECATION") - when { - isPersistenceEnabled -> LocalCacheSettings.Persistent(cacheSizeBytes) - cacheSizeBytes == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED -> LocalCacheSettings.Memory(MemoryGarbageCollectorSettings.Eager) - else -> LocalCacheSettings.Memory(MemoryGarbageCollectorSettings.LRUGC(cacheSizeBytes)) - } - }, - callbackExecutorMap[native] ?: TaskExecutors.MAIN_THREAD - ) - } - set(value) { - native.firestoreSettings = androidFirestoreSettings { - isSslEnabled = value.sslEnabled - host = value.host - setLocalCacheSettings(value.cacheSettings.android) - } - callbackExecutorMap[native] = value.callbackExecutor - } - - actual fun collection(collectionPath: String) = native.collection(collectionPath) - - actual fun collectionGroup(collectionId: String) = native.collectionGroup(collectionId) - - actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - - actual fun batch() = native.batch() - - actual fun setLoggingEnabled(loggingEnabled: Boolean) = - com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) - - actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T = - native.runTransaction { runBlocking { it.func() } }.await() - - actual suspend fun clearPersistence() = - native.clearPersistence().await().run { } - - actual fun useEmulator(host: String, port: Int) { - native.useEmulator(host, port) - } - - actual suspend fun disableNetwork() = - native.disableNetwork().await().run { } - - actual suspend fun enableNetwork() = - native.enableNetwork().await().run { } - -} val FirebaseFirestore.android get() = native @@ -176,288 +87,21 @@ actual fun firestoreSettings( } }.apply(builder).build() -internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { - is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() - is SetOptions.Overwrite -> null - is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) - is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) -} - actual typealias NativeWriteBatch = com.google.firebase.firestore.WriteBatch -@PublishedApi -internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeWriteBatchWrapper = (setOptions.android?.let { - native.set(documentRef.android, encodedData.android, it) - } ?: native.set(documentRef.android, encodedData.android)).let { - this - } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - native.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - native.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - native.delete(documentRef.android).let { this } - - actual suspend fun commit() { - native.commit().await() - } -} - val WriteBatch.android get() = native actual typealias NativeTransaction = com.google.firebase.firestore.Transaction -@PublishedApi -internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeTransactionWrapper { - setOptions.android?.let { - native.set(documentRef.android, encodedData.android, it) - } ?: native.set(documentRef.android, encodedData.android) - return this - } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - native.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - native.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - native.delete(documentRef.android).let { this } - - actual suspend fun get(documentRef: DocumentReference) = - NativeDocumentSnapshotWrapper(native.get(documentRef.android)) -} - val Transaction.android get() = native /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = com.google.firebase.firestore.DocumentReference -@PublishedApi -internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { - val android: NativeDocumentReferenceType by ::nativeValue - actual val id: String - get() = android.id - - actual val path: String - get() = android.path - - actual val parent: NativeCollectionReferenceWrapper - get() = NativeCollectionReferenceWrapper(android.parent) - - actual fun collection(collectionPath: String) = android.collection(collectionPath) - - actual suspend fun get(source: Source) = - android.get(source.toAndroidSource()).await() - - actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) { - val task = (setOptions.android?.let { - android.set(encodedData.android, it) - } ?: android.set(encodedData.android)) - task.await() - } - - actual suspend fun updateEncoded(encodedData: EncodedObject) { - android.update(encodedData.android).await() - } - - actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { - android.update(encodedFieldsAndValues.toMap()) - }?.await() - } - - actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - android.update(field, value, *moreFieldsAndValues) - }?.await() - } - - actual suspend fun delete() { - android.delete().await() - } - - actual val snapshots: Flow get() = snapshots() - - actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception -> - snapshot?.let { trySend(snapshot) } - exception?.let { close(exception) } - } - - override fun equals(other: Any?): Boolean = - this === other || other is NativeDocumentReference && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - private fun addSnapshotListener( - includeMetadataChanges: Boolean = false, - listener: ProducerScope.(com.google.firebase.firestore.DocumentSnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit - ) = callbackFlow { - val executor = callbackExecutorMap[android.firestore] ?: TaskExecutors.MAIN_THREAD - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val registration = android.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> - listener(snapshots, exception) - } - awaitClose { registration.remove() } - } -} - val DocumentReference.android get() = native.android actual typealias NativeQuery = AndroidQuery -@PublishedApi -internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: AndroidQuery) { - - actual fun limit(limit: Number) = native.limit(limit.toLong()) - - actual val snapshots get() = callbackFlow { - val listener = native.addSnapshotListener { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val metadataChanges = - if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = native.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - actual suspend fun get(source: Source): QuerySnapshot = QuerySnapshot(native.get(source.toAndroidSource()).await()) - - actual fun where(filter: Filter) = native.where(filter.toAndroidFilter()) - - private fun Filter.toAndroidFilter(): AndroidFilter = when (this) { - is Filter.And -> AndroidFilter.and(*filters.map { it.toAndroidFilter() }.toTypedArray()) - is Filter.Or -> AndroidFilter.or(*filters.map { it.toAndroidFilter() }.toTypedArray()) - is Filter.Field -> { - when (constraint) { - is WhereConstraint.ForNullableObject -> { - val modifier: (String, Any?) -> AndroidFilter = when (constraint) { - is WhereConstraint.EqualTo -> AndroidFilter::equalTo - is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo - } - modifier.invoke(field, constraint.safeValue) - } - is WhereConstraint.ForObject -> { - val modifier: (String, Any) -> AndroidFilter = when (constraint) { - is WhereConstraint.LessThan -> AndroidFilter::lessThan - is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan - is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo - is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo - is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains - } - modifier.invoke(field, constraint.safeValue) - } - is WhereConstraint.ForArray -> { - val modifier: (String, List) -> AndroidFilter = when (constraint) { - is WhereConstraint.InArray -> AndroidFilter::inArray - is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny - is WhereConstraint.NotInArray -> AndroidFilter::notInArray - } - modifier.invoke(field, constraint.safeValues) - } - } - } - is Filter.Path -> { - when (constraint) { - is WhereConstraint.ForNullableObject -> { - val modifier: (AndroidFieldPath, Any?) -> AndroidFilter = when (constraint) { - is WhereConstraint.EqualTo -> AndroidFilter::equalTo - is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo - } - modifier.invoke(path.android, constraint.safeValue) - } - is WhereConstraint.ForObject -> { - val modifier: (AndroidFieldPath, Any) -> AndroidFilter = when (constraint) { - is WhereConstraint.LessThan -> AndroidFilter::lessThan - is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan - is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo - is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo - is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains - } - modifier.invoke(path.android, constraint.safeValue) - } - is WhereConstraint.ForArray -> { - val modifier: (AndroidFieldPath, List) -> AndroidFilter = when (constraint) { - is WhereConstraint.InArray -> AndroidFilter::inArray - is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny - is WhereConstraint.NotInArray -> AndroidFilter::notInArray - } - modifier.invoke(path.android, constraint.safeValues) - } - } - } - } - - actual fun orderBy(field: String, direction: Direction) = native.orderBy(field, direction) - actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.orderBy(field, direction) - - actual fun startAfter(document: NativeDocumentSnapshot) = native.startAfter(document) - actual fun startAfter(vararg fieldValues: Any) = native.startAfter(*fieldValues) - actual fun startAt(document: NativeDocumentSnapshot) = native.startAt(document) - actual fun startAt(vararg fieldValues: Any) = native.startAt(*fieldValues) - - actual fun endBefore(document: NativeDocumentSnapshot) = native.endBefore(document) - actual fun endBefore(vararg fieldValues: Any) = native.endBefore(*fieldValues) - actual fun endAt(document: NativeDocumentSnapshot) = native.endAt(document) - actual fun endAt(vararg fieldValues: Any) = native.endAt(*fieldValues) - - private fun addSnapshotListener( - includeMetadataChanges: Boolean = false, - listener: ProducerScope.(com.google.firebase.firestore.QuerySnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit - ) = callbackFlow { - val executor = callbackExecutorMap[native.firestore] ?: TaskExecutors.MAIN_THREAD - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val registration = native.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> - listener(snapshots, exception) - } - awaitClose { registration.remove() } - } -} - val Query.android get() = native actual typealias Direction = com.google.firebase.firestore.Query.Direction @@ -465,23 +109,6 @@ actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type actual typealias NativeCollectionReference = com.google.firebase.firestore.CollectionReference -@PublishedApi -internal actual class NativeCollectionReferenceWrapper internal actual constructor(override actual val native: NativeCollectionReference) : NativeQueryWrapper(native) { - - actual val path: String - get() = native.path - - actual val document: NativeDocumentReference - get() = NativeDocumentReference(native.document()) - - actual val parent: NativeDocumentReference? - get() = native.parent?.let{ NativeDocumentReference(it) } - - actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(native.add(data.android).await()) -} - val CollectionReference.android get() = native actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException @@ -511,30 +138,6 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC actual typealias NativeDocumentSnapshot = com.google.firebase.firestore.DocumentSnapshot -@PublishedApi -internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: com.google.firebase.firestore.DocumentSnapshot) { - - actual val id get() = native.id - actual val reference get() = NativeDocumentReference(native.reference) - - actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(field, serverTimestampBehavior.toAndroid()) - actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(fieldPath, serverTimestampBehavior.toAndroid()) - actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = native.getData(serverTimestampBehavior.toAndroid()) - - actual fun contains(field: String) = native.contains(field) - actual fun contains(fieldPath: EncodedFieldPath) = native.contains(fieldPath) - - actual val exists get() = native.exists() - - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) - - fun ServerTimestampBehavior.toAndroid(): com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior = when (this) { - ServerTimestampBehavior.ESTIMATE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.ESTIMATE - ServerTimestampBehavior.NONE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.NONE - ServerTimestampBehavior.PREVIOUS -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.PREVIOUS - } -} - val DocumentSnapshot.android get() = native actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { @@ -564,9 +167,3 @@ actual class FieldPath private constructor(val android: com.google.firebase.fire actual typealias EncodedFieldPath = com.google.firebase.firestore.FieldPath internal typealias NativeSource = com.google.firebase.firestore.Source - -private fun Source.toAndroidSource() = when(this) { - CACHE -> NativeSource.CACHE - SERVER -> NativeSource.SERVER - DEFAULT -> NativeSource.DEFAULT -} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..0d41e1e13 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,25 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android +import kotlinx.coroutines.tasks.await + +@PublishedApi +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + actual val path: String + get() = native.path + + actual val document: NativeDocumentReference + get() = NativeDocumentReference(native.document()) + + actual val parent: NativeDocumentReference? + get() = native.parent?.let{ NativeDocumentReference(it) } + + actual fun document(documentPath: String) = + NativeDocumentReference(native.document(documentPath)) + + actual suspend fun addEncoded(data: EncodedObject) = + NativeDocumentReference(native.add(data.android).await()) +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..b0553e921 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,88 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.android.gms.tasks.TaskExecutors +import com.google.firebase.firestore.MetadataChanges +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.tasks.await + +@PublishedApi +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + val android: NativeDocumentReferenceType by ::nativeValue + actual val id: String + get() = android.id + + actual val path: String + get() = android.path + + actual val parent: NativeCollectionReferenceWrapper + get() = NativeCollectionReferenceWrapper(android.parent) + + actual fun collection(collectionPath: String) = android.collection(collectionPath) + + actual suspend fun get(source: Source) = + android.get(source.toAndroidSource()).await() + + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) { + val task = (setOptions.android?.let { + android.set(encodedData.android, it) + } ?: android.set(encodedData.android)) + task.await() + } + + actual suspend fun updateEncoded(encodedData: EncodedObject) { + android.update(encodedData.android).await() + } + + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { + android.update(encodedFieldsAndValues.toMap()) + }?.await() + } + + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + android.update(field, value, *moreFieldsAndValues) + }?.await() + } + + actual suspend fun delete() { + android.delete().await() + } + + actual val snapshots: Flow get() = snapshots() + + actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception -> + snapshot?.let { trySend(snapshot) } + exception?.let { close(exception) } + } + + override fun equals(other: Any?): Boolean = + this === other || other is NativeDocumentReference && nativeValue == other.nativeValue + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() + + private fun addSnapshotListener( + includeMetadataChanges: Boolean = false, + listener: ProducerScope.(com.google.firebase.firestore.DocumentSnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit + ) = callbackFlow { + val executor = callbackExecutorMap[android.firestore] ?: TaskExecutors.MAIN_THREAD + val metadataChanges = + if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val registration = + android.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> + listener(snapshots, exception) + } + awaitClose { registration.remove() } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..70ec6128b --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,29 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata + +@PublishedApi +internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: com.google.firebase.firestore.DocumentSnapshot) { + + actual val id get() = native.id + actual val reference get() = NativeDocumentReference(native.reference) + + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(field, serverTimestampBehavior.toAndroid()) + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(fieldPath, serverTimestampBehavior.toAndroid()) + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = native.getData(serverTimestampBehavior.toAndroid()) + + actual fun contains(field: String) = native.contains(field) + actual fun contains(fieldPath: EncodedFieldPath) = native.contains(fieldPath) + + actual val exists get() = native.exists() + + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) + + fun ServerTimestampBehavior.toAndroid(): com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior = when (this) { + ServerTimestampBehavior.ESTIMATE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.ESTIMATE + ServerTimestampBehavior.NONE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.NONE + ServerTimestampBehavior.PREVIOUS -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.PREVIOUS + } +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..189666ff5 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,101 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.android.gms.tasks.TaskExecutors +import com.google.firebase.firestore.MemoryCacheSettings +import com.google.firebase.firestore.MemoryEagerGcSettings +import com.google.firebase.firestore.MemoryLruGcSettings +import com.google.firebase.firestore.PersistentCacheSettings +import com.google.firebase.firestore.firestoreSettings +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.LocalCacheSettings +import dev.gitlive.firebase.firestore.MemoryGarbageCollectorSettings +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.android +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.tasks.await + +internal actual class NativeFirebaseFirestoreWrapper actual constructor(actual val native: NativeFirebaseFirestore) { + + actual var settings: FirebaseFirestoreSettings + get() = with(native.firestoreSettings) { + FirebaseFirestoreSettings( + isSslEnabled, + host, + cacheSettings?.let { localCacheSettings -> + when (localCacheSettings) { + is MemoryCacheSettings -> { + val garbageCollectionSettings = + when (val settings = localCacheSettings.garbageCollectorSettings) { + is MemoryEagerGcSettings -> MemoryGarbageCollectorSettings.Eager + is MemoryLruGcSettings -> MemoryGarbageCollectorSettings.LRUGC( + settings.sizeBytes + ) + + else -> throw IllegalArgumentException("Existing settings does not have valid GarbageCollectionSettings") + } + LocalCacheSettings.Memory(garbageCollectionSettings) + } + + is PersistentCacheSettings -> LocalCacheSettings.Persistent( + localCacheSettings.sizeBytes + ) + + else -> throw IllegalArgumentException("Existing settings is not of a valid type") + } + } ?: kotlin.run { + @Suppress("DEPRECATION") + when { + isPersistenceEnabled -> LocalCacheSettings.Persistent(cacheSizeBytes) + cacheSizeBytes == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED -> LocalCacheSettings.Memory( + MemoryGarbageCollectorSettings.Eager + ) + + else -> LocalCacheSettings.Memory( + MemoryGarbageCollectorSettings.LRUGC( + cacheSizeBytes + ) + ) + } + }, + callbackExecutorMap[native] ?: TaskExecutors.MAIN_THREAD + ) + } + set(value) { + native.firestoreSettings = firestoreSettings { + isSslEnabled = value.sslEnabled + host = value.host + setLocalCacheSettings(value.cacheSettings.android) + } + callbackExecutorMap[native] = value.callbackExecutor + } + + actual fun collection(collectionPath: String) = native.collection(collectionPath) + + actual fun collectionGroup(collectionId: String) = native.collectionGroup(collectionId) + + actual fun document(documentPath: String) = + NativeDocumentReference(native.document(documentPath)) + + actual fun batch() = native.batch() + + actual fun setLoggingEnabled(loggingEnabled: Boolean) = + com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) + + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T = + native.runTransaction { runBlocking { it.func() } }.await() + + actual suspend fun clearPersistence() = + native.clearPersistence().await().run { } + + actual fun useEmulator(host: String, port: Int) { + native.useEmulator(host, port) + } + + actual suspend fun disableNetwork() = + native.disableNetwork().await().run { } + + actual suspend fun enableNetwork() = + native.enableNetwork().await().run { } + +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..349c1fd9f --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,138 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.android.gms.tasks.TaskExecutors +import com.google.firebase.firestore.FieldPath +import com.google.firebase.firestore.MetadataChanges +import com.google.firebase.firestore.Query +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.WhereConstraint +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.tasks.await + +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: Query) { + + actual fun limit(limit: Number) = native.limit(limit.toLong()) + + actual val snapshots get() = callbackFlow { + val listener = native.addSnapshotListener { snapshot, exception -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + exception?.let { close(exception) } + } + awaitClose { listener.remove() } + } + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val metadataChanges = + if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val listener = native.addSnapshotListener(metadataChanges) { snapshot, exception -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + exception?.let { close(exception) } + } + awaitClose { listener.remove() } + } + + actual suspend fun get(source: Source): QuerySnapshot = + QuerySnapshot(native.get(source.toAndroidSource()).await()) + + actual fun where(filter: Filter) = native.where(filter.toAndroidFilter()) + + private fun Filter.toAndroidFilter(): com.google.firebase.firestore.Filter = when (this) { + is Filter.And -> com.google.firebase.firestore.Filter.and(*filters.map { it.toAndroidFilter() } + .toTypedArray()) + is Filter.Or -> com.google.firebase.firestore.Filter.or(*filters.map { it.toAndroidFilter() } + .toTypedArray()) + is Filter.Field -> { + when (constraint) { + is WhereConstraint.ForNullableObject -> { + val modifier: (String, Any?) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.EqualTo -> com.google.firebase.firestore.Filter::equalTo + is WhereConstraint.NotEqualTo -> com.google.firebase.firestore.Filter::notEqualTo + } + modifier.invoke(field, constraint.safeValue) + } + is WhereConstraint.ForObject -> { + val modifier: (String, Any) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.LessThan -> com.google.firebase.firestore.Filter::lessThan + is WhereConstraint.GreaterThan -> com.google.firebase.firestore.Filter::greaterThan + is WhereConstraint.LessThanOrEqualTo -> com.google.firebase.firestore.Filter::lessThanOrEqualTo + is WhereConstraint.GreaterThanOrEqualTo -> com.google.firebase.firestore.Filter::greaterThanOrEqualTo + is WhereConstraint.ArrayContains -> com.google.firebase.firestore.Filter::arrayContains + } + modifier.invoke(field, constraint.safeValue) + } + is WhereConstraint.ForArray -> { + val modifier: (String, List) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.InArray -> com.google.firebase.firestore.Filter::inArray + is WhereConstraint.ArrayContainsAny -> com.google.firebase.firestore.Filter::arrayContainsAny + is WhereConstraint.NotInArray -> com.google.firebase.firestore.Filter::notInArray + } + modifier.invoke(field, constraint.safeValues) + } + } + } + is Filter.Path -> { + when (constraint) { + is WhereConstraint.ForNullableObject -> { + val modifier: (FieldPath, Any?) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.EqualTo -> com.google.firebase.firestore.Filter::equalTo + is WhereConstraint.NotEqualTo -> com.google.firebase.firestore.Filter::notEqualTo + } + modifier.invoke(path.android, constraint.safeValue) + } + is WhereConstraint.ForObject -> { + val modifier: (FieldPath, Any) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.LessThan -> com.google.firebase.firestore.Filter::lessThan + is WhereConstraint.GreaterThan -> com.google.firebase.firestore.Filter::greaterThan + is WhereConstraint.LessThanOrEqualTo -> com.google.firebase.firestore.Filter::lessThanOrEqualTo + is WhereConstraint.GreaterThanOrEqualTo -> com.google.firebase.firestore.Filter::greaterThanOrEqualTo + is WhereConstraint.ArrayContains -> com.google.firebase.firestore.Filter::arrayContains + } + modifier.invoke(path.android, constraint.safeValue) + } + is WhereConstraint.ForArray -> { + val modifier: (FieldPath, List) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.InArray -> com.google.firebase.firestore.Filter::inArray + is WhereConstraint.ArrayContainsAny -> com.google.firebase.firestore.Filter::arrayContainsAny + is WhereConstraint.NotInArray -> com.google.firebase.firestore.Filter::notInArray + } + modifier.invoke(path.android, constraint.safeValues) + } + } + } + } + + actual fun orderBy(field: String, direction: Direction) = native.orderBy(field, direction) + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.orderBy(field, direction) + + actual fun startAfter(document: NativeDocumentSnapshot) = native.startAfter(document) + actual fun startAfter(vararg fieldValues: Any) = native.startAfter(*fieldValues) + actual fun startAt(document: NativeDocumentSnapshot) = native.startAt(document) + actual fun startAt(vararg fieldValues: Any) = native.startAt(*fieldValues) + + actual fun endBefore(document: NativeDocumentSnapshot) = native.endBefore(document) + actual fun endBefore(vararg fieldValues: Any) = native.endBefore(*fieldValues) + actual fun endAt(document: NativeDocumentSnapshot) = native.endAt(document) + actual fun endAt(vararg fieldValues: Any) = native.endAt(*fieldValues) + + private fun addSnapshotListener( + includeMetadataChanges: Boolean = false, + listener: ProducerScope.(com.google.firebase.firestore.QuerySnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit + ) = callbackFlow { + val executor = callbackExecutorMap[native.firestore] ?: TaskExecutors.MAIN_THREAD + val metadataChanges = + if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val registration = + native.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> + listener(snapshots, exception) + } + awaitClose { registration.remove() } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..6db5b7ff3 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,46 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.android +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android + +@PublishedApi +internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeTransactionWrapper { + setOptions.android?.let { + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android) + return this + } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + native.delete(documentRef.android).let { this } + + actual suspend fun get(documentRef: DocumentReference) = + NativeDocumentSnapshotWrapper(native.get(documentRef.android)) +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..1c6c042b6 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,47 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.android +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android +import kotlinx.coroutines.tasks.await + +@PublishedApi +internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeWriteBatchWrapper = (setOptions.android?.let { + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android)).let { + this + } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + native.delete(documentRef.android).let { this } + + actual suspend fun commit() { + native.commit().await() + } +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt new file mode 100644 index 000000000..8b44d4799 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt @@ -0,0 +1,8 @@ +package dev.gitlive.firebase.firestore.internal + +internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { + is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() + is SetOptions.Overwrite -> null + is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) + is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt new file mode 100644 index 000000000..3b0d71950 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt @@ -0,0 +1,10 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeSource +import dev.gitlive.firebase.firestore.Source + +internal fun Source.toAndroidSource() = when(this) { + Source.CACHE -> NativeSource.CACHE + Source.SERVER -> NativeSource.SERVER + Source.DEFAULT -> NativeSource.DEFAULT +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt new file mode 100644 index 000000000..a728f542c --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt @@ -0,0 +1,8 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.firebase.firestore.FirebaseFirestore +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executor + +// Since on iOS Callback threads are set as settings, we store the settings explicitly here as well +internal val callbackExecutorMap = ConcurrentHashMap() diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt index 79951b4a3..4a62715a4 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt @@ -1,5 +1,6 @@ package dev.gitlive.firebase.firestore +import dev.gitlive.firebase.firestore.internal.NativeDocumentReference import dev.gitlive.firebase.internal.FirebaseEncoder import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt index e6897c6d7..6be86251c 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt @@ -1,5 +1,7 @@ package dev.gitlive.firebase.firestore +import dev.gitlive.firebase.firestore.internal.safeValue + sealed interface WhereConstraint { sealed interface ForNullableObject : WhereConstraint { diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index b6cc94999..15a9528b5 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -10,6 +10,15 @@ import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.firestore.internal.NativeCollectionReferenceWrapper +import dev.gitlive.firebase.firestore.internal.NativeDocumentReference +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper +import dev.gitlive.firebase.firestore.internal.NativeFirebaseFirestoreWrapper +import dev.gitlive.firebase.firestore.internal.NativeQueryWrapper +import dev.gitlive.firebase.firestore.internal.NativeTransactionWrapper +import dev.gitlive.firebase.firestore.internal.NativeWriteBatchWrapper +import dev.gitlive.firebase.firestore.internal.SetOptions +import dev.gitlive.firebase.firestore.internal.safeValue import dev.gitlive.firebase.internal.decode import dev.gitlive.firebase.internal.encodeAsObject import kotlinx.coroutines.flow.Flow @@ -27,22 +36,6 @@ expect fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore expect class NativeFirebaseFirestore -internal expect class NativeFirebaseFirestoreWrapper internal constructor(native: NativeFirebaseFirestore) { - val native: NativeFirebaseFirestore - var settings: FirebaseFirestoreSettings - - fun collection(collectionPath: String): NativeCollectionReference - fun collectionGroup(collectionId: String): NativeQuery - fun document(documentPath: String): NativeDocumentReference - fun batch(): NativeWriteBatch - fun setLoggingEnabled(loggingEnabled: Boolean) - suspend fun clearPersistence() - suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T - fun useEmulator(host: String, port: Int) - suspend fun disableNetwork() - suspend fun enableNetwork() -} - class FirebaseFirestore internal constructor(private val wrapper: NativeFirebaseFirestoreWrapper) { constructor(native: NativeFirebaseFirestore) : this(NativeFirebaseFirestoreWrapper(native)) @@ -117,29 +110,7 @@ expect class FirebaseFirestoreSettings { expect fun firestoreSettings(settings: FirebaseFirestoreSettings? = null, builder: FirebaseFirestoreSettings.Builder.() -> Unit): FirebaseFirestoreSettings -@PublishedApi -internal sealed class SetOptions { - data object Merge : SetOptions() - data object Overwrite : SetOptions() - data class MergeFields(val fields: List) : SetOptions() - data class MergeFieldPaths(val fieldPaths: List) : SetOptions() { - val encodedFieldPaths = fieldPaths.map { it.encoded } - } -} - expect class NativeTransaction -@PublishedApi -internal expect class NativeTransactionWrapper internal constructor(native: NativeTransaction) { - - val native: NativeTransaction - - fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeTransactionWrapper - fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper - fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper - fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper - fun delete(documentRef: DocumentReference): NativeTransactionWrapper - suspend fun get(documentRef: DocumentReference): NativeDocumentSnapshotWrapper -} data class Transaction internal constructor(@PublishedApi internal val nativeWrapper: NativeTransactionWrapper) { @@ -219,32 +190,6 @@ data class Transaction internal constructor(@PublishedApi internal val nativeWra expect open class NativeQuery -@PublishedApi -internal expect open class NativeQueryWrapper internal constructor(native: NativeQuery) { - - open val native: NativeQuery - - fun limit(limit: Number): NativeQuery - val snapshots: Flow - fun snapshots(includeMetadataChanges: Boolean = false): Flow - suspend fun get(source: Source = Source.DEFAULT): QuerySnapshot - - fun where(filter: Filter): NativeQuery - - fun orderBy(field: String, direction: Direction): NativeQuery - fun orderBy(field: EncodedFieldPath, direction: Direction): NativeQuery - - fun startAfter(document: NativeDocumentSnapshot): NativeQuery - fun startAfter(vararg fieldValues: Any): NativeQuery - fun startAt(document: NativeDocumentSnapshot): NativeQuery - fun startAt(vararg fieldValues: Any): NativeQuery - - fun endBefore(document: NativeDocumentSnapshot): NativeQuery - fun endBefore(vararg fieldValues: Any): NativeQuery - fun endAt(document: NativeDocumentSnapshot): NativeQuery - fun endAt(vararg fieldValues: Any): NativeQuery -} - open class Query internal constructor(internal val nativeQuery: NativeQueryWrapper) { constructor(native: NativeQuery) : this(NativeQueryWrapper(native)) @@ -324,28 +269,8 @@ fun Query.where(path: FieldPath, inArray: List? = null, arrayContainsAny: L ) } -internal val Any.safeValue: Any get() = when (this) { - is Timestamp -> nativeValue - is GeoPoint -> nativeValue - is DocumentReference -> native.nativeValue - is Map<*, *> -> this.mapNotNull { (key, value) -> key?.let { it.safeValue to value?.safeValue } } - is Collection<*> -> this.mapNotNull { it?.safeValue } - else -> this -} - expect class NativeWriteBatch -@PublishedApi -internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch) { - val native: NativeWriteBatch - fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeWriteBatchWrapper - fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper - fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper - fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper - fun delete(documentRef: DocumentReference): NativeWriteBatchWrapper - suspend fun commit() -} - data class WriteBatch internal constructor(@PublishedApi internal val nativeWrapper: NativeWriteBatchWrapper) { constructor(native: NativeWriteBatch) : this(NativeWriteBatchWrapper(native)) @@ -432,24 +357,6 @@ data class WriteBatch internal constructor(@PublishedApi internal val nativeWrap /** A class representing a platform specific Firebase DocumentReference. */ expect class NativeDocumentReferenceType -@PublishedApi -internal expect class NativeDocumentReference(nativeValue: NativeDocumentReferenceType) { - val nativeValue: NativeDocumentReferenceType - val id: String - val path: String - val snapshots: Flow - val parent: NativeCollectionReferenceWrapper - fun snapshots(includeMetadataChanges: Boolean = false): Flow - - fun collection(collectionPath: String): NativeCollectionReference - suspend fun get(source: Source = Source.DEFAULT): NativeDocumentSnapshot - suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) - suspend fun updateEncoded(encodedData: EncodedObject) - suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) - suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) - suspend fun delete() -} - /** A class representing a Firebase DocumentReference. */ @Serializable(with = DocumentReferenceSerializer::class) data class DocumentReference internal constructor(@PublishedApi internal val native: NativeDocumentReference) { @@ -532,19 +439,6 @@ data class DocumentReference internal constructor(@PublishedApi internal val nat expect class NativeCollectionReference : NativeQuery -@PublishedApi -internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : NativeQueryWrapper { - - override val native: NativeCollectionReference - - val path: String - val document: NativeDocumentReference - val parent: NativeDocumentReference? - - fun document(documentPath: String): NativeDocumentReference - suspend fun addEncoded(data: EncodedObject): NativeDocumentReference -} - data class CollectionReference internal constructor(@PublishedApi internal val nativeWrapper: NativeCollectionReferenceWrapper) : Query(nativeWrapper) { constructor(native: NativeCollectionReference) : this(NativeCollectionReferenceWrapper(native)) @@ -627,23 +521,6 @@ expect class DocumentChange { } expect class NativeDocumentSnapshot -@PublishedApi -internal expect class NativeDocumentSnapshotWrapper internal constructor(native: NativeDocumentSnapshot) { - - val native: NativeDocumentSnapshot - - val exists: Boolean - val id: String - val reference: NativeDocumentReference - val metadata: SnapshotMetadata - - fun contains(field: String): Boolean - fun contains(fieldPath: EncodedFieldPath): Boolean - - fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? -} data class DocumentSnapshot internal constructor(@PublishedApi internal val nativeWrapper: NativeDocumentSnapshotWrapper) { diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..f4b38be4f --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,18 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.internal.EncodedObject + +@PublishedApi +internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : + NativeQueryWrapper { + + override val native: NativeCollectionReference + + val path: String + val document: NativeDocumentReference + val parent: NativeDocumentReference? + + fun document(documentPath: String): NativeDocumentReference + suspend fun addEncoded(data: EncodedObject): NativeDocumentReference +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..b50248ae9 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,27 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.internal.EncodedObject +import kotlinx.coroutines.flow.Flow + +@PublishedApi +internal expect class NativeDocumentReference(nativeValue: NativeDocumentReferenceType) { + val nativeValue: NativeDocumentReferenceType + val id: String + val path: String + val snapshots: Flow + val parent: NativeCollectionReferenceWrapper + fun snapshots(includeMetadataChanges: Boolean = false): Flow + + fun collection(collectionPath: String): NativeCollectionReference + suspend fun get(source: Source = Source.DEFAULT): NativeDocumentSnapshot + suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) + suspend fun updateEncoded(encodedData: EncodedObject) + suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) + suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) + suspend fun delete() +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..5db75d0e2 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,24 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata + +@PublishedApi +internal expect class NativeDocumentSnapshotWrapper internal constructor(native: NativeDocumentSnapshot) { + + val native: NativeDocumentSnapshot + + val exists: Boolean + val id: String + val reference: NativeDocumentReference + val metadata: SnapshotMetadata + + fun contains(field: String): Boolean + fun contains(fieldPath: EncodedFieldPath): Boolean + + fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..dc6957177 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,24 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.NativeWriteBatch + +internal expect class NativeFirebaseFirestoreWrapper internal constructor(native: NativeFirebaseFirestore) { + val native: NativeFirebaseFirestore + var settings: FirebaseFirestoreSettings + + fun collection(collectionPath: String): NativeCollectionReference + fun collectionGroup(collectionId: String): NativeQuery + fun document(documentPath: String): NativeDocumentReference + fun batch(): NativeWriteBatch + fun setLoggingEnabled(loggingEnabled: Boolean) + suspend fun clearPersistence() + suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T + fun useEmulator(host: String, port: Int) + suspend fun disableNetwork() + suspend fun enableNetwork() +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..8a2d8228b --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,36 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import kotlinx.coroutines.flow.Flow + +@PublishedApi +internal expect open class NativeQueryWrapper internal constructor(native: NativeQuery) { + + open val native: NativeQuery + + fun limit(limit: Number): NativeQuery + val snapshots: Flow + fun snapshots(includeMetadataChanges: Boolean = false): Flow + suspend fun get(source: Source = Source.DEFAULT): QuerySnapshot + + fun where(filter: Filter): NativeQuery + + fun orderBy(field: String, direction: Direction): NativeQuery + fun orderBy(field: EncodedFieldPath, direction: Direction): NativeQuery + + fun startAfter(document: NativeDocumentSnapshot): NativeQuery + fun startAfter(vararg fieldValues: Any): NativeQuery + fun startAt(document: NativeDocumentSnapshot): NativeQuery + fun startAt(vararg fieldValues: Any): NativeQuery + + fun endBefore(document: NativeDocumentSnapshot): NativeQuery + fun endBefore(vararg fieldValues: Any): NativeQuery + fun endAt(document: NativeDocumentSnapshot): NativeQuery + fun endAt(vararg fieldValues: Any): NativeQuery +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..abc7776a2 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,19 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.internal.EncodedObject + +@PublishedApi +internal expect class NativeTransactionWrapper internal constructor(native: NativeTransaction) { + + val native: NativeTransaction + + fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeTransactionWrapper + fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper + fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper + fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper + fun delete(documentRef: DocumentReference): NativeTransactionWrapper + suspend fun get(documentRef: DocumentReference): NativeDocumentSnapshotWrapper +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..60a2e9564 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,17 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.internal.EncodedObject + +@PublishedApi +internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch) { + val native: NativeWriteBatch + fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeWriteBatchWrapper + fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper + fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper + fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper + fun delete(documentRef: DocumentReference): NativeWriteBatchWrapper + suspend fun commit() +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt new file mode 100644 index 000000000..f424b244d --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt @@ -0,0 +1,14 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.GeoPoint +import dev.gitlive.firebase.firestore.Timestamp + +internal val Any.safeValue: Any get() = when (this) { + is Timestamp -> nativeValue + is GeoPoint -> nativeValue + is DocumentReference -> native.nativeValue + is Map<*, *> -> this.mapNotNull { (key, value) -> key?.let { it.safeValue to value?.safeValue } } + is Collection<*> -> this.mapNotNull { it?.safeValue } + else -> this +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt new file mode 100644 index 000000000..84fcbe604 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt @@ -0,0 +1,13 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.FieldPath + +@PublishedApi +internal sealed class SetOptions { + data object Merge : SetOptions() + data object Overwrite : SetOptions() + data class MergeFields(val fields: List) : SetOptions() + data class MergeFieldPaths(val fieldPaths: List) : SetOptions() { + val encodedFieldPaths = fieldPaths.map { it.encoded } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index c9b9867bc..3f81e6cd2 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -9,15 +9,10 @@ import cocoapods.FirebaseFirestoreInternal.FIRDocumentChangeType.* import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -import dev.gitlive.firebase.internal.EncodedObject -import dev.gitlive.firebase.internal.ios +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper import kotlinx.cinterop.* import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.runBlocking import platform.Foundation.NSError -import platform.Foundation.NSNull import platform.Foundation.NSNumber import platform.Foundation.numberWithLong import platform.darwin.dispatch_get_main_queue @@ -42,52 +37,6 @@ val LocalCacheSettings.ios: FIRLocalCacheSettingsProtocol get() = when (this) { actual typealias NativeFirebaseFirestore = FIRFirestore -@Suppress("UNCHECKED_CAST") -internal actual class NativeFirebaseFirestoreWrapper internal actual constructor(actual val native: NativeFirebaseFirestore) { - - actual var settings: FirebaseFirestoreSettings = firestoreSettings { }.also { - native.settings = it.ios - } - set(value) { - field = value - native.settings = value.ios - } - - actual fun collection(collectionPath: String) = native.collectionWithPath(collectionPath) - - actual fun collectionGroup(collectionId: String) = native.collectionGroupWithID(collectionId) - - actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - - actual fun batch() = native.batch() - - actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = - FIRFirestore.enableLogging(loggingEnabled) - - actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = - awaitResult { native.runTransactionWithBlock({ transaction, _ -> runBlocking { transaction!!.func() } }, it) } as T - - actual suspend fun clearPersistence() = - await { native.clearPersistenceWithCompletion(it) } - - actual fun useEmulator(host: String, port: Int) { - native.useEmulatorWithHost(host, port.toLong()) - settings = firestoreSettings(settings) { - this.host = "$host:$port" - cacheSettings = memoryCacheSettings { } - sslEnabled = false - } - } - - actual suspend fun disableNetwork() { - await { native.disableNetworkWithCompletion(it) } - } - - actual suspend fun enableNetwork() { - await { native.enableNetworkWithCompletion(it) } - } -} - val FirebaseFirestore.ios get() = native actual data class FirebaseFirestoreSettings( @@ -150,249 +99,23 @@ actual fun firestoreSettings( actual typealias NativeWriteBatch = FIRWriteBatch -@PublishedApi -internal actual class NativeWriteBatchWrapper actual constructor(actual val native: NativeWriteBatch) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeWriteBatchWrapper = when (setOptions) { - is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) - is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) - is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) - }.let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatchWrapper = native.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatchWrapper = native.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun delete(documentRef: DocumentReference) = - native.deleteDocument(documentRef.ios).let { this } - - actual suspend fun commit() = await { native.commitWithCompletion(it) } -} - val WriteBatch.ios get() = native actual typealias NativeTransaction = FIRTransaction -@PublishedApi -internal actual class NativeTransactionWrapper actual constructor(actual val native: FIRTransaction) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeTransactionWrapper = when (setOptions) { - is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) - is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) - is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) - }.let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransactionWrapper = native.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransactionWrapper = native.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun delete(documentRef: DocumentReference) = - native.deleteDocument(documentRef.ios).let { this } - - actual suspend fun get(documentRef: DocumentReference) = - throwError { NativeDocumentSnapshotWrapper(native.getDocument(documentRef.ios, it)!!) } - -} - val Transaction.ios get() = native /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = FIRDocumentReference -@PublishedApi -internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> - snapshot?.let { trySend(snapshot) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - val ios: NativeDocumentReferenceType by ::nativeValue - - actual val id: String - get() = ios.documentID - - actual val path: String - get() = ios.path - - actual val parent: NativeCollectionReferenceWrapper - get() = NativeCollectionReferenceWrapper(ios.parent) - - - actual fun collection(collectionPath: String) = ios.collectionWithPath(collectionPath) - - actual suspend fun get(source: Source) = - awaitResult { ios.getDocumentWithSource(source.toIosSource(), it) } - - actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = await { - when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData.ios, true, it) - is SetOptions.Overwrite -> ios.setData(encodedData.ios, false, it) - is SetOptions.MergeFields -> ios.setData(encodedData.ios, setOptions.fields, it) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData.ios, setOptions.encodedFieldPaths, it) - } - } - - actual suspend fun updateEncoded(encodedData: EncodedObject) = await { - ios.updateData(encodedData.ios, it) - } - - actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = await { - ios.updateData(encodedFieldsAndValues.toMap(), it) - } - - actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) = await { - ios.updateData(encodedFieldsAndValues.toMap(), it) - } - - actual suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } - - actual val snapshots get() = callbackFlow { - val listener = ios.addSnapshotListener { snapshot, error -> - snapshot?.let { trySend(snapshot) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - override fun equals(other: Any?): Boolean = - this === other || other is NativeDocumentReference && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() -} - val DocumentReference.ios get() = native.ios actual typealias NativeQuery = FIRQuery -@PublishedApi -internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { - - actual fun limit(limit: Number) = native.queryLimitedTo(limit.toLong()) - - actual suspend fun get(source: Source) = QuerySnapshot(awaitResult { native.getDocumentsWithSource(source.toIosSource(),it) }) - - actual val snapshots get() = callbackFlow { - val listener = native.addSnapshotListener { snapshot, error -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = native.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - actual fun where(filter: Filter) = native.queryWhereFilter(filter.toFIRFilter()) - - private fun Filter.toFIRFilter(): FIRFilter = when (this) { - is Filter.And -> FIRFilter.andFilterWithFilters(filters.map { it.toFIRFilter() }) - is Filter.Or -> FIRFilter.orFilterWithFilters(filters.map { it.toFIRFilter() }) - is Filter.Field -> when (constraint) { - is WhereConstraint.EqualTo -> FIRFilter.filterWhereField(field, isEqualTo = constraint.safeValue ?: NSNull.`null`()) - is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereField(field, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) - is WhereConstraint.LessThan -> FIRFilter.filterWhereField(field, isLessThan = constraint.safeValue) - is WhereConstraint.GreaterThan -> FIRFilter.filterWhereField(field, isGreaterThan = constraint.safeValue) - is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereField(field, isLessThanOrEqualTo = constraint.safeValue) - is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereField(field, isGreaterThanOrEqualTo = constraint.safeValue) - is WhereConstraint.ArrayContains -> FIRFilter.filterWhereField(field, arrayContains = constraint.safeValue) - is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereField(field, arrayContainsAny = constraint.safeValues) - is WhereConstraint.InArray -> FIRFilter.filterWhereField(field, `in` = constraint.safeValues) - is WhereConstraint.NotInArray -> FIRFilter.filterWhereField(field, notIn = constraint.safeValues) - } - is Filter.Path -> when (constraint) { - is WhereConstraint.EqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isEqualTo = constraint.safeValue ?: NSNull.`null`()) - is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) - is WhereConstraint.LessThan -> FIRFilter.filterWhereFieldPath(path.ios, isLessThan = constraint.safeValue) - is WhereConstraint.GreaterThan -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThan = constraint.safeValue) - is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isLessThanOrEqualTo = constraint.safeValue) - is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThanOrEqualTo = constraint.safeValue) - is WhereConstraint.ArrayContains -> FIRFilter.filterWhereFieldPath(path.ios, arrayContains = constraint.safeValue) - is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereFieldPath(path.ios, arrayContainsAny = constraint.safeValues) - is WhereConstraint.InArray -> FIRFilter.filterWhereFieldPath(path.ios, `in` = constraint.safeValues) - is WhereConstraint.NotInArray -> FIRFilter.filterWhereFieldPath(path.ios, notIn = constraint.safeValues) - } - } - - actual fun orderBy(field: String, direction: Direction) = native.queryOrderedByField(field, direction == Direction.DESCENDING) - actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.queryOrderedByFieldPath(field, direction == Direction.DESCENDING) - - actual fun startAfter(document: NativeDocumentSnapshot) = native.queryStartingAfterDocument(document) - actual fun startAfter(vararg fieldValues: Any) = native.queryStartingAfterValues(fieldValues.asList()) - actual fun startAt(document: NativeDocumentSnapshot) = native.queryStartingAtDocument(document) - actual fun startAt(vararg fieldValues: Any) = native.queryStartingAtValues(fieldValues.asList()) - - actual fun endBefore(document: NativeDocumentSnapshot) = native.queryEndingBeforeDocument(document) - actual fun endBefore(vararg fieldValues: Any) = native.queryEndingBeforeValues(fieldValues.asList()) - actual fun endAt(document: NativeDocumentSnapshot) = native.queryEndingAtDocument(document) - actual fun endAt(vararg fieldValues: Any) = native.queryEndingAtValues(fieldValues.asList()) -} - val Query.ios get() = native actual typealias NativeCollectionReference = FIRCollectionReference -@PublishedApi -internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { - - actual val path: String - get() = native.path - - actual val document get() = NativeDocumentReference(native.documentWithAutoID()) - - actual val parent get() = native.parent?.let{ NativeDocumentReference(it) } - - actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { native.addDocumentWithData(data.ios, it) }) -} - val CollectionReference.ios get() = native actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) @@ -475,40 +198,6 @@ actual class DocumentChange(val ios: FIRDocumentChange) { actual typealias NativeDocumentSnapshot = FIRDocumentSnapshot -@PublishedApi -internal actual class NativeDocumentSnapshotWrapper actual constructor(actual val native: NativeDocumentSnapshot) { - - actual val id get() = native.documentID - - actual val reference get() = NativeDocumentReference(native.reference) - - actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = - native.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - - // Despite its name implying otherwise, valueForField accepts both a String representation of a Field and a FIRFieldPath - actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = - native.valueForField(fieldPath, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - - actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = - native.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) - ?.mapValues { (_, value) -> - value?.takeIf { it !is NSNull } - } - - actual fun contains(field: String) = native.valueForField(field) != null - actual fun contains(fieldPath: EncodedFieldPath) = native.valueForField(fieldPath) != null - - actual val exists get() = native.exists - - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) - - fun ServerTimestampBehavior.toIos() : FIRServerTimestampBehavior = when (this) { - ServerTimestampBehavior.ESTIMATE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorEstimate - ServerTimestampBehavior.NONE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorNone - ServerTimestampBehavior.PREVIOUS -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorPrevious - } -} - val DocumentSnapshot.ios get() = native actual class SnapshotMetadata(val ios: FIRSnapshotMetadata) { @@ -530,18 +219,6 @@ actual class FieldPath private constructor(val ios: FIRFieldPath) { actual typealias EncodedFieldPath = FIRFieldPath -private fun T.throwError(block: T.(errorPointer: CPointer>) -> R): R { - memScoped { - val errorPointer: CPointer> = alloc>().ptr - val result = block(errorPointer) - val error: NSError? = errorPointer.pointed.value - if (error != null) { - throw error.toException() - } - return result - } -} - suspend inline fun awaitResult(function: (callback: (T?, NSError?) -> Unit) -> Unit): T { val job = CompletableDeferred() function { result, error -> @@ -566,9 +243,3 @@ suspend inline fun await(function: (callback: (NSError?) -> Unit) -> T): T { job.await() return result } - -private fun Source.toIosSource() = when (this) { - Source.CACHE -> FIRFirestoreSource.FIRFirestoreSourceCache - Source.SERVER -> FIRFirestoreSource.FIRFirestoreSourceServer - Source.DEFAULT -> FIRFirestoreSource.FIRFirestoreSourceDefault -} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..339a88385 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,23 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios + +@PublishedApi +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + actual val path: String + get() = native.path + + actual val document get() = NativeDocumentReference(native.documentWithAutoID()) + + actual val parent get() = native.parent?.let{ NativeDocumentReference(it) } + + actual fun document(documentPath: String) = + NativeDocumentReference(native.documentWithPath(documentPath)) + + actual suspend fun addEncoded(data: EncodedObject) = + NativeDocumentReference(await { native.addDocumentWithData(data.ios, it) }) +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..cd6f8fa31 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,85 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.firestore.awaitResult +import dev.gitlive.firebase.firestore.toException +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow + +@PublishedApi +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val listener = + ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> + snapshot?.let { trySend(snapshot) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + val ios: NativeDocumentReferenceType by ::nativeValue + + actual val id: String + get() = ios.documentID + + actual val path: String + get() = ios.path + + actual val parent: NativeCollectionReferenceWrapper + get() = NativeCollectionReferenceWrapper(ios.parent) + + + actual fun collection(collectionPath: String) = ios.collectionWithPath(collectionPath) + + actual suspend fun get(source: Source) = + awaitResult { ios.getDocumentWithSource(source.toIosSource(), it) } + + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = await { + when (setOptions) { + is SetOptions.Merge -> ios.setData(encodedData.ios, true, it) + is SetOptions.Overwrite -> ios.setData(encodedData.ios, false, it) + is SetOptions.MergeFields -> ios.setData(encodedData.ios, setOptions.fields, it) + is SetOptions.MergeFieldPaths -> ios.setData( + encodedData.ios, + setOptions.encodedFieldPaths, + it + ) + } + } + + actual suspend fun updateEncoded(encodedData: EncodedObject) = await { + ios.updateData(encodedData.ios, it) + } + + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = + await { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } + + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) = + await { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } + + actual suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } + + actual val snapshots get() = callbackFlow { + val listener = ios.addSnapshotListener { snapshot, error -> + snapshot?.let { trySend(snapshot) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + override fun equals(other: Any?): Boolean = + this === other || other is NativeDocumentReference && nativeValue == other.nativeValue + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..929a906ce --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,42 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRServerTimestampBehavior +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata +import platform.Foundation.NSNull + +@PublishedApi +internal actual class NativeDocumentSnapshotWrapper actual constructor(actual val native: NativeDocumentSnapshot) { + + actual val id get() = native.documentID + + actual val reference get() = NativeDocumentReference(native.reference) + + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + + // Despite its name implying otherwise, valueForField accepts both a String representation of a Field and a FIRFieldPath + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.valueForField(fieldPath, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) + ?.mapValues { (_, value) -> + value?.takeIf { it !is NSNull } + } + + actual fun contains(field: String) = native.valueForField(field) != null + actual fun contains(fieldPath: EncodedFieldPath) = native.valueForField(fieldPath) != null + + actual val exists get() = native.exists + + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) + + fun ServerTimestampBehavior.toIos() : FIRServerTimestampBehavior = when (this) { + ServerTimestampBehavior.ESTIMATE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorEstimate + ServerTimestampBehavior.NONE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorNone + ServerTimestampBehavior.PREVIOUS -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorPrevious + } +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..e95be1050 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,63 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRFirestore +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.firestore.awaitResult +import dev.gitlive.firebase.firestore.firestoreSettings +import dev.gitlive.firebase.firestore.memoryCacheSettings +import kotlinx.coroutines.runBlocking + +@Suppress("UNCHECKED_CAST") +internal actual class NativeFirebaseFirestoreWrapper internal actual constructor(actual val native: NativeFirebaseFirestore) { + + actual var settings: FirebaseFirestoreSettings = firestoreSettings { }.also { + native.settings = it.ios + } + set(value) { + field = value + native.settings = value.ios + } + + actual fun collection(collectionPath: String) = native.collectionWithPath(collectionPath) + + actual fun collectionGroup(collectionId: String) = native.collectionGroupWithID(collectionId) + + actual fun document(documentPath: String) = + NativeDocumentReference(native.documentWithPath(documentPath)) + + actual fun batch() = native.batch() + + actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = + FIRFirestore.enableLogging(loggingEnabled) + + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = + awaitResult { + native.runTransactionWithBlock( + { transaction, _ -> runBlocking { transaction!!.func() } }, + it + ) + } as T + + actual suspend fun clearPersistence() = + await { native.clearPersistenceWithCompletion(it) } + + actual fun useEmulator(host: String, port: Int) { + native.useEmulatorWithHost(host, port.toLong()) + settings = firestoreSettings(settings) { + this.host = "$host:$port" + cacheSettings = memoryCacheSettings { } + sslEnabled = false + } + } + + actual suspend fun disableNetwork() { + await { native.disableNetworkWithCompletion(it) } + } + + actual suspend fun enableNetwork() { + await { native.enableNetworkWithCompletion(it) } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..4a050998e --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,86 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRFilter +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.WhereConstraint +import dev.gitlive.firebase.firestore.awaitResult +import dev.gitlive.firebase.firestore.toException +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import platform.Foundation.NSNull + +@PublishedApi +internal actual open class NativeQueryWrapper internal actual constructor(actual open val native: NativeQuery) { + + actual fun limit(limit: Number) = native.queryLimitedTo(limit.toLong()) + + actual suspend fun get(source: Source) = + QuerySnapshot(awaitResult { native.getDocumentsWithSource(source.toIosSource(), it) }) + + actual val snapshots get() = callbackFlow { + val listener = native.addSnapshotListener { snapshot, error -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val listener = + native.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + actual fun where(filter: Filter) = native.queryWhereFilter(filter.toFIRFilter()) + + private fun Filter.toFIRFilter(): FIRFilter = when (this) { + is Filter.And -> FIRFilter.andFilterWithFilters(filters.map { it.toFIRFilter() }) + is Filter.Or -> FIRFilter.orFilterWithFilters(filters.map { it.toFIRFilter() }) + is Filter.Field -> when (constraint) { + is WhereConstraint.EqualTo -> FIRFilter.filterWhereField(field, isEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereField(field, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.LessThan -> FIRFilter.filterWhereField(field, isLessThan = constraint.safeValue) + is WhereConstraint.GreaterThan -> FIRFilter.filterWhereField(field, isGreaterThan = constraint.safeValue) + is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereField(field, isLessThanOrEqualTo = constraint.safeValue) + is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereField(field, isGreaterThanOrEqualTo = constraint.safeValue) + is WhereConstraint.ArrayContains -> FIRFilter.filterWhereField(field, arrayContains = constraint.safeValue) + is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereField(field, arrayContainsAny = constraint.safeValues) + is WhereConstraint.InArray -> FIRFilter.filterWhereField(field, `in` = constraint.safeValues) + is WhereConstraint.NotInArray -> FIRFilter.filterWhereField(field, notIn = constraint.safeValues) + } + is Filter.Path -> when (constraint) { + is WhereConstraint.EqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.LessThan -> FIRFilter.filterWhereFieldPath(path.ios, isLessThan = constraint.safeValue) + is WhereConstraint.GreaterThan -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThan = constraint.safeValue) + is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isLessThanOrEqualTo = constraint.safeValue) + is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThanOrEqualTo = constraint.safeValue) + is WhereConstraint.ArrayContains -> FIRFilter.filterWhereFieldPath(path.ios, arrayContains = constraint.safeValue) + is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereFieldPath(path.ios, arrayContainsAny = constraint.safeValues) + is WhereConstraint.InArray -> FIRFilter.filterWhereFieldPath(path.ios, `in` = constraint.safeValues) + is WhereConstraint.NotInArray -> FIRFilter.filterWhereFieldPath(path.ios, notIn = constraint.safeValues) + } + } + + actual fun orderBy(field: String, direction: Direction) = native.queryOrderedByField(field, direction == Direction.DESCENDING) + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.queryOrderedByFieldPath(field, direction == Direction.DESCENDING) + + actual fun startAfter(document: NativeDocumentSnapshot) = native.queryStartingAfterDocument(document) + actual fun startAfter(vararg fieldValues: Any) = native.queryStartingAfterValues(fieldValues.asList()) + actual fun startAt(document: NativeDocumentSnapshot) = native.queryStartingAtDocument(document) + actual fun startAt(vararg fieldValues: Any) = native.queryStartingAtValues(fieldValues.asList()) + + actual fun endBefore(document: NativeDocumentSnapshot) = native.queryEndingBeforeDocument(document) + actual fun endBefore(vararg fieldValues: Any) = native.queryEndingBeforeValues(fieldValues.asList()) + actual fun endAt(document: NativeDocumentSnapshot) = native.queryEndingAtDocument(document) + actual fun endAt(vararg fieldValues: Any) = native.queryEndingAtValues(fieldValues.asList()) +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..809275539 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,48 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRTransaction +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.ios +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios + +@PublishedApi +internal actual class NativeTransactionWrapper actual constructor(actual val native: FIRTransaction) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeTransactionWrapper = when (setOptions) { + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) + }.let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeTransactionWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeTransactionWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + actual fun delete(documentRef: DocumentReference) = + native.deleteDocument(documentRef.ios).let { this } + + actual suspend fun get(documentRef: DocumentReference) = + throwError { NativeDocumentSnapshotWrapper(native.getDocument(documentRef.ios, it)!!) } + +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..628096433 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,47 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.firestore.ios +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios + +@PublishedApi +internal actual class NativeWriteBatchWrapper actual constructor(actual val native: NativeWriteBatch) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeWriteBatchWrapper = when (setOptions) { + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) + }.let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeWriteBatchWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeWriteBatchWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + actual fun delete(documentRef: DocumentReference) = + native.deleteDocument(documentRef.ios).let { this } + + actual suspend fun commit() = await { native.commitWithCompletion(it) } +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt new file mode 100644 index 000000000..aa9665ac9 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt @@ -0,0 +1,10 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRFirestoreSource +import dev.gitlive.firebase.firestore.Source + +internal fun Source.toIosSource() = when (this) { + Source.CACHE -> FIRFirestoreSource.FIRFirestoreSourceCache + Source.SERVER -> FIRFirestoreSource.FIRFirestoreSourceServer + Source.DEFAULT -> FIRFirestoreSource.FIRFirestoreSourceDefault +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt new file mode 100644 index 000000000..a298833f8 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt @@ -0,0 +1,23 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.toException +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.ObjCObjectVar +import kotlinx.cinterop.alloc +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.ptr +import kotlinx.cinterop.value +import platform.Foundation.NSError + +internal fun T.throwError(block: T.(errorPointer: CPointer>) -> R): R { + memScoped { + val errorPointer: CPointer> = alloc>().ptr + val result = block(errorPointer) + val error: NSError? = errorPointer.pointed.value + if (error != null) { + throw error.toException() + } + return result + } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 418371bb4..f51f9888b 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -10,43 +10,21 @@ import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.externals.getApp import dev.gitlive.firebase.firestore.externals.MemoryCacheSettings import dev.gitlive.firebase.firestore.externals.PersistentCacheSettings -import dev.gitlive.firebase.firestore.externals.QueryConstraint -import dev.gitlive.firebase.firestore.externals.addDoc -import dev.gitlive.firebase.firestore.externals.and -import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence -import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator -import dev.gitlive.firebase.firestore.externals.deleteDoc -import dev.gitlive.firebase.firestore.externals.doc import dev.gitlive.firebase.firestore.externals.getDoc import dev.gitlive.firebase.firestore.externals.getDocFromCache import dev.gitlive.firebase.firestore.externals.getDocFromServer import dev.gitlive.firebase.firestore.externals.getDocs import dev.gitlive.firebase.firestore.externals.getDocsFromCache import dev.gitlive.firebase.firestore.externals.getDocsFromServer -import dev.gitlive.firebase.firestore.externals.initializeFirestore import dev.gitlive.firebase.firestore.externals.memoryEagerGarbageCollector import dev.gitlive.firebase.firestore.externals.memoryLocalCache import dev.gitlive.firebase.firestore.externals.memoryLruGarbageCollector -import dev.gitlive.firebase.firestore.externals.onSnapshot -import dev.gitlive.firebase.firestore.externals.or -import dev.gitlive.firebase.firestore.externals.orderBy import dev.gitlive.firebase.firestore.externals.persistentLocalCache -import dev.gitlive.firebase.firestore.externals.query -import dev.gitlive.firebase.firestore.externals.refEqual -import dev.gitlive.firebase.firestore.externals.setDoc -import dev.gitlive.firebase.firestore.externals.setLogLevel -import dev.gitlive.firebase.firestore.externals.writeBatch -import dev.gitlive.firebase.internal.EncodedObject -import dev.gitlive.firebase.internal.js -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.await -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.promise +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper +import dev.gitlive.firebase.firestore.internal.NativeFirebaseFirestoreWrapper +import dev.gitlive.firebase.firestore.internal.SetOptions import kotlin.js.Json import kotlin.js.json -import dev.gitlive.firebase.externals.FirebaseApp as JsFirebaseApp import dev.gitlive.firebase.firestore.externals.Firestore as JsFirestore import dev.gitlive.firebase.firestore.externals.CollectionReference as JsCollectionReference import dev.gitlive.firebase.firestore.externals.DocumentChange as JsDocumentChange @@ -58,19 +36,7 @@ import dev.gitlive.firebase.firestore.externals.QuerySnapshot as JsQuerySnapshot import dev.gitlive.firebase.firestore.externals.SnapshotMetadata as JsSnapshotMetadata import dev.gitlive.firebase.firestore.externals.Transaction as JsTransaction import dev.gitlive.firebase.firestore.externals.WriteBatch as JsWriteBatch -import dev.gitlive.firebase.firestore.externals.collection as jsCollection -import dev.gitlive.firebase.firestore.externals.collectionGroup as jsCollectionGroup -import dev.gitlive.firebase.firestore.externals.disableNetwork as jsDisableNetwork import dev.gitlive.firebase.firestore.externals.documentId as jsDocumentId -import dev.gitlive.firebase.firestore.externals.enableNetwork as jsEnableNetwork -import dev.gitlive.firebase.firestore.externals.endAt as jsEndAt -import dev.gitlive.firebase.firestore.externals.endBefore as jsEndBefore -import dev.gitlive.firebase.firestore.externals.limit as jsLimit -import dev.gitlive.firebase.firestore.externals.runTransaction as jsRunTransaction -import dev.gitlive.firebase.firestore.externals.startAfter as jsStartAfter -import dev.gitlive.firebase.firestore.externals.startAt as jsStartAt -import dev.gitlive.firebase.firestore.externals.updateDoc as jsUpdate -import dev.gitlive.firebase.firestore.externals.where as jsWhere actual val Firebase.firestore get() = rethrow { FirebaseFirestore(NativeFirebaseFirestoreWrapper(getApp())) } @@ -80,76 +46,6 @@ actual fun Firebase.firestore(app: FirebaseApp) = actual data class NativeFirebaseFirestore(val js: JsFirestore) -internal actual class NativeFirebaseFirestoreWrapper internal constructor( - private val createNative: NativeFirebaseFirestoreWrapper.() -> NativeFirebaseFirestore -){ - - internal actual constructor(native: NativeFirebaseFirestore) : this({ native }) - internal constructor(app: JsFirebaseApp) : this( - { - NativeFirebaseFirestore( - initializeFirestore(app, settings.js).also { - emulatorSettings?.run { - connectFirestoreEmulator(it, host, port) - } - } - ) - } - ) - - private data class EmulatorSettings(val host: String, val port: Int) - - actual var settings: FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().build() - set(value) { - if (lazyNative.isInitialized()) { - throw IllegalStateException("FirebaseFirestore has already been started and its settings can no longer be changed. You can only call setFirestoreSettings() before calling any other methods on a FirebaseFirestore object.") - } else { - field = value - } - } - private var emulatorSettings: EmulatorSettings? = null - - // initializeFirestore must be called before any call, including before `getFirestore()` - // To allow settings to be updated, we defer creating the wrapper until the first call to `native` - private val lazyNative = lazy { - createNative() - } - actual val native: NativeFirebaseFirestore by lazyNative - private val js get() = native.js - - actual fun collection(collectionPath: String) = rethrow { NativeCollectionReference(jsCollection(js, collectionPath)) } - - actual fun collectionGroup(collectionId: String) = rethrow { NativeQuery(jsCollectionGroup(js, collectionId)) } - - actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } - - actual fun batch() = rethrow { NativeWriteBatch(writeBatch(js)) } - - actual fun setLoggingEnabled(loggingEnabled: Boolean) = - rethrow { setLogLevel( if(loggingEnabled) "error" else "silent") } - - actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = - rethrow { jsRunTransaction(js, { GlobalScope.promise { NativeTransaction(it).func() } } ).await() } - - actual suspend fun clearPersistence() = - rethrow { clearIndexedDbPersistence(js).await() } - - actual fun useEmulator(host: String, port: Int) = rethrow { - settings = firestoreSettings(settings) { - this.host = "$host:$port" - } - emulatorSettings = EmulatorSettings(host, port) - } - - actual suspend fun disableNetwork() { - rethrow { jsDisableNetwork(js).await() } - } - - actual suspend fun enableNetwork() { - rethrow { jsEnableNetwork(js).await() } - } -} - val FirebaseFirestore.js: JsFirestore get() = native.js actual data class FirebaseFirestoreSettings( @@ -215,297 +111,26 @@ actual fun firestoreSettings( } }.apply(builder).build() -internal val SetOptions.js: Json get() = when (this) { - is SetOptions.Merge -> json("merge" to true) - is SetOptions.Overwrite -> json("merge" to false) - is SetOptions.MergeFields -> json("mergeFields" to fields.toTypedArray()) - is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) -} - actual data class NativeWriteBatch(val js: JsWriteBatch) -@PublishedApi -internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { - - constructor(js: JsWriteBatch) : this(NativeWriteBatch(js)) - - val js = native.js - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.js, setOptions.js) }.let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.js) } - .let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatchWrapper = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatchWrapper = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - rethrow { js.delete(documentRef.js) } - .let { this } - - actual suspend fun commit() = rethrow { js.commit().await() } -} - val WriteBatch.js get() = native.js actual data class NativeTransaction(val js: JsTransaction) -@PublishedApi -internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { - - constructor(js: JsTransaction) : this(NativeTransaction(js)) - - val js = native.js - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeTransactionWrapper = rethrow { - js.set(documentRef.js, encodedData.js, setOptions.js) - } - .let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.js) } - .let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransactionWrapper = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransactionWrapper = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - rethrow { js.delete(documentRef.js) } - .let { this } - - actual suspend fun get(documentRef: DocumentReference) = - rethrow { NativeDocumentSnapshotWrapper(js.get(documentRef.js).await()) } -} - val Transaction.js get() = native.js /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = JsDocumentReference -@PublishedApi -internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { - val js: NativeDocumentReferenceType = nativeValue - - actual val id: String - get() = rethrow { js.id } - - actual val path: String - get() = rethrow { js.path } - - actual val parent: NativeCollectionReferenceWrapper - get() = rethrow { NativeCollectionReferenceWrapper(js.parent) } - - actual fun collection(collectionPath: String) = rethrow { NativeCollectionReference(jsCollection(js, collectionPath)) } - - actual suspend fun get(source: Source) = rethrow { NativeDocumentSnapshot( js.get(source).await()) } - - actual val snapshots: Flow get() = snapshots() - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val unsubscribe = onSnapshot( - js, - json("includeMetadataChanges" to includeMetadataChanges), - { trySend(NativeDocumentSnapshot(it)) }, - { close(errorToException(it)) } - ) - awaitClose { unsubscribe() } - } - - actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { - setDoc(js, encodedData.js, setOptions.js).await() - } - - actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData.js).await() } - - actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { - rethrow { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - jsUpdate(js, field, value, *moreFieldsAndValues) - } - ?.await() - } - } - - actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { - rethrow { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - jsUpdate(js, field, value, *moreFieldsAndValues) - }?.await() - } - } - - actual suspend fun delete() = rethrow { deleteDoc(js).await() } - - override fun equals(other: Any?): Boolean = - this === other || other is NativeDocumentReference && refEqual(nativeValue, other.nativeValue) - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = "DocumentReference(path=$path)" -} - val DocumentReference.js get() = native.js actual open class NativeQuery(open val js: JsQuery) internal val JsQuery.wrapped get() = NativeQuery(this) -@PublishedApi -internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { - - constructor(js: JsQuery) : this(NativeQuery(js)) - - open val js: JsQuery get() = native.js - - actual suspend fun get(source: Source) = rethrow { QuerySnapshot(js.get(source).await()) } - - actual fun limit(limit: Number) = query(js, jsLimit(limit)).wrapped - - actual fun where(filter: Filter) = query(js, filter.toQueryConstraint()).wrapped - - private fun Filter.toQueryConstraint(): QueryConstraint = when (this) { - is Filter.And -> and(*filters.map { it.toQueryConstraint() }.toTypedArray()) - is Filter.Or -> or(*filters.map { it.toQueryConstraint() }.toTypedArray()) - is Filter.Field -> { - val value = when (constraint) { - is WhereConstraint.ForNullableObject -> constraint.safeValue - is WhereConstraint.ForObject -> constraint.safeValue - is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() - } - jsWhere(field, constraint.filterOp, value) - } - is Filter.Path -> { - val value = when (constraint) { - is WhereConstraint.ForNullableObject -> constraint.safeValue - is WhereConstraint.ForObject -> constraint.safeValue - is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() - } - jsWhere(path.js, constraint.filterOp, value) - } - } - - private val WhereConstraint.filterOp: String get() = when (this) { - is WhereConstraint.EqualTo -> "==" - is WhereConstraint.NotEqualTo -> "!=" - is WhereConstraint.LessThan -> "<" - is WhereConstraint.LessThanOrEqualTo -> "<=" - is WhereConstraint.GreaterThan -> ">" - is WhereConstraint.GreaterThanOrEqualTo -> ">=" - is WhereConstraint.ArrayContains -> "array-contains" - is WhereConstraint.ArrayContainsAny -> "array-contains-any" - is WhereConstraint.InArray -> "in" - is WhereConstraint.NotInArray -> "not-in" - } - - actual fun orderBy(field: String, direction: Direction) = rethrow { - query(js, orderBy(field, direction.jsString)).wrapped - } - - actual fun orderBy(field: EncodedFieldPath, direction: Direction) = rethrow { - query(js, orderBy(field, direction.jsString)).wrapped - } - - actual fun startAfter(document: NativeDocumentSnapshot) = rethrow { query(js, jsStartAfter(document.js)).wrapped } - - actual fun startAfter(vararg fieldValues: Any) = rethrow { query(js, jsStartAfter(*fieldValues)).wrapped } - - actual fun startAt(document: NativeDocumentSnapshot) = rethrow { query(js, jsStartAt(document.js)).wrapped } - - actual fun startAt(vararg fieldValues: Any) = rethrow { query(js, jsStartAt(*fieldValues)).wrapped } - - actual fun endBefore(document: NativeDocumentSnapshot) = rethrow { query(js, jsEndBefore(document.js)).wrapped } - - actual fun endBefore(vararg fieldValues: Any) = rethrow { query(js, jsEndBefore(*fieldValues)).wrapped } - - actual fun endAt(document: NativeDocumentSnapshot) = rethrow { query(js, jsEndAt(document.js)).wrapped } - - actual fun endAt(vararg fieldValues: Any) = rethrow { query(js, jsEndAt(*fieldValues)).wrapped } - - actual val snapshots get() = callbackFlow { - val unsubscribe = rethrow { - onSnapshot( - js, - { trySend(QuerySnapshot(it)) }, - { close(errorToException(it)) } - ) - } - awaitClose { rethrow { unsubscribe() } } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val unsubscribe = rethrow { - onSnapshot( - js, - json("includeMetadataChanges" to includeMetadataChanges), - { trySend(QuerySnapshot(it)) }, - { close(errorToException(it)) } - ) - } - awaitClose { rethrow { unsubscribe() } } - } -} - val Query.js get() = native.js actual data class NativeCollectionReference(override val js: JsCollectionReference) : NativeQuery(js) -@PublishedApi -internal actual class NativeCollectionReferenceWrapper actual internal constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { - - constructor(js: JsCollectionReference) : this(NativeCollectionReference(js)) - - override val js: JsCollectionReference = native.js - - actual val path: String - get() = rethrow { js.path } - - actual val document get() = rethrow { NativeDocumentReference(doc(js)) } - - actual val parent get() = rethrow { js.parent?.let{ NativeDocumentReference(it) } } - - actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } - - actual suspend fun addEncoded(data: EncodedObject) = rethrow { - NativeDocumentReference(addDoc(js, data.js).await()) - } -} - val CollectionReference.js get() = native.js actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) @@ -534,37 +159,6 @@ actual class DocumentChange(val js: JsDocumentChange) { actual data class NativeDocumentSnapshot(val js: JsDocumentSnapshot) -@PublishedApi -internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: NativeDocumentSnapshot) { - - constructor(js: JsDocumentSnapshot) : this(NativeDocumentSnapshot(js)) - - val js: JsDocumentSnapshot = native.js - - actual val id get() = rethrow { js.id } - actual val reference get() = rethrow { NativeDocumentReference(js.ref) } - - actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { - js.get(field, getTimestampsOptions(serverTimestampBehavior)) - } - - actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { - js.get(fieldPath, getTimestampsOptions(serverTimestampBehavior)) - } - - actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { - js.data(getTimestampsOptions(serverTimestampBehavior)) - } - - actual fun contains(field: String) = rethrow { js.get(field) != undefined } - actual fun contains(fieldPath: EncodedFieldPath) = rethrow { js.get(fieldPath) != undefined } - actual val exists get() = rethrow { js.exists() } - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) - - fun getTimestampsOptions(serverTimestampBehavior: ServerTimestampBehavior) = - json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) -} - val DocumentSnapshot.js get() = native.js actual class SnapshotMetadata(val js: JsSnapshotMetadata) { @@ -589,14 +183,6 @@ actual class FieldPath private constructor(val js: JsFieldPath) { actual typealias EncodedFieldPath = JsFieldPath -//actual data class FirebaseFirestoreSettings internal constructor( -// val cacheSizeBytes: Number? = undefined, -// val host: String? = undefined, -// val ssl: Boolean? = undefined, -// var timestampsInSnapshots: Boolean? = undefined, -// var enablePersistence: Boolean = false -//) - actual enum class FirestoreExceptionCode { OK, CANCELLED, @@ -677,15 +263,3 @@ fun entriesOf(jsObject: dynamic): List> = // from: https://discuss.kotlinlang.org/t/how-to-access-native-js-object-as-a-map-string-any/509/8 fun mapOf(jsObject: dynamic): Map = entriesOf(jsObject).toMap() - -private fun NativeDocumentReferenceType.get(source: Source) = when (source) { - Source.DEFAULT -> getDoc(this) - Source.CACHE -> getDocFromCache(this) - Source.SERVER -> getDocFromServer(this) -} - -private fun JsQuery.get(source: Source) = when (source) { - Source.DEFAULT -> getDocs(this) - Source.CACHE -> getDocsFromCache(this) - Source.SERVER -> getDocsFromServer(this) -} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/JSQueryGet.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/JSQueryGet.kt new file mode 100644 index 000000000..51524558a --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/JSQueryGet.kt @@ -0,0 +1,7 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.externals.Query +import dev.gitlive.firebase.firestore.externals.getDocs +import dev.gitlive.firebase.firestore.externals.getDocsFromCache +import dev.gitlive.firebase.firestore.externals.getDocsFromServer diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..f3f475006 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,38 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.externals.CollectionReference +import dev.gitlive.firebase.firestore.externals.addDoc +import dev.gitlive.firebase.firestore.externals.doc +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await + +@PublishedApi +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + constructor(js: CollectionReference) : this(NativeCollectionReference(js)) + + override val js: CollectionReference = native.js + + actual val path: String + get() = rethrow { js.path } + + actual val document get() = rethrow { NativeDocumentReference(doc(js)) } + + actual val parent get() = rethrow { js.parent?.let{ NativeDocumentReference(it) } } + + actual fun document(documentPath: String) = rethrow { + NativeDocumentReference( + doc( + js, + documentPath + ) + ) + } + + actual suspend fun addEncoded(data: EncodedObject) = rethrow { + NativeDocumentReference(addDoc(js, data.js).await()) + } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..f38a8faf0 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,110 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.errorToException +import dev.gitlive.firebase.firestore.externals.deleteDoc +import dev.gitlive.firebase.firestore.externals.getDoc +import dev.gitlive.firebase.firestore.externals.getDocFromCache +import dev.gitlive.firebase.firestore.externals.getDocFromServer +import dev.gitlive.firebase.firestore.externals.onSnapshot +import dev.gitlive.firebase.firestore.externals.refEqual +import dev.gitlive.firebase.firestore.externals.setDoc +import dev.gitlive.firebase.firestore.externals.updateDoc +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlin.js.json + +@PublishedApi +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + val js: NativeDocumentReferenceType = nativeValue + + actual val id: String + get() = rethrow { js.id } + + actual val path: String + get() = rethrow { js.path } + + actual val parent: NativeCollectionReferenceWrapper + get() = rethrow { NativeCollectionReferenceWrapper(js.parent) } + + actual fun collection(collectionPath: String) = rethrow { + NativeCollectionReference( + dev.gitlive.firebase.firestore.externals.collection( + js, + collectionPath + ) + ) + } + + actual suspend fun get(source: Source) = rethrow { + NativeDocumentSnapshot( + js.get(source).await() + ) + } + + actual val snapshots: Flow get() = snapshots() + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val unsubscribe = onSnapshot( + js, + json("includeMetadataChanges" to includeMetadataChanges), + { trySend(NativeDocumentSnapshot(it)) }, + { close(errorToException(it)) } + ) + awaitClose { unsubscribe() } + } + + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { + setDoc(js, encodedData.js, setOptions.js).await() + } + + actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { updateDoc( + js, + encodedData.js + ).await() } + + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + rethrow { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + updateDoc(js, field, value, *moreFieldsAndValues) + } + ?.await() + } + } + + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + rethrow { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + updateDoc(js, field, value, *moreFieldsAndValues) + }?.await() + } + } + + actual suspend fun delete() = rethrow { deleteDoc(js).await() } + + override fun equals(other: Any?): Boolean = + this === other || other is NativeDocumentReference && refEqual( + nativeValue, + other.nativeValue + ) + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = "DocumentReference(path=$path)" +} + +private fun NativeDocumentReferenceType.get(source: Source) = when (source) { + Source.DEFAULT -> getDoc(this) + Source.CACHE -> getDocFromCache(this) + Source.SERVER -> getDocFromServer(this) +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..dfc83769a --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,40 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata +import dev.gitlive.firebase.firestore.externals.DocumentSnapshot +import dev.gitlive.firebase.firestore.rethrow +import kotlin.js.json + +@PublishedApi +internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: NativeDocumentSnapshot) { + + constructor(js: DocumentSnapshot) : this(NativeDocumentSnapshot(js)) + + val js: DocumentSnapshot = native.js + + actual val id get() = rethrow { js.id } + actual val reference get() = rethrow { NativeDocumentReference(js.ref) } + + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.get(field, getTimestampsOptions(serverTimestampBehavior)) + } + + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.get(fieldPath, getTimestampsOptions(serverTimestampBehavior)) + } + + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.data(getTimestampsOptions(serverTimestampBehavior)) + } + + actual fun contains(field: String) = rethrow { js.get(field) != undefined } + actual fun contains(fieldPath: EncodedFieldPath) = rethrow { js.get(fieldPath) != undefined } + actual val exists get() = rethrow { js.exists() } + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) + + fun getTimestampsOptions(serverTimestampBehavior: ServerTimestampBehavior) = + json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..4c186869d --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,113 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.externals.FirebaseApp +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence +import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator +import dev.gitlive.firebase.firestore.externals.doc +import dev.gitlive.firebase.firestore.externals.initializeFirestore +import dev.gitlive.firebase.firestore.externals.setLogLevel +import dev.gitlive.firebase.firestore.externals.writeBatch +import dev.gitlive.firebase.firestore.firestoreSettings +import dev.gitlive.firebase.firestore.rethrow +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.await +import kotlinx.coroutines.promise + +internal actual class NativeFirebaseFirestoreWrapper internal constructor( + private val createNative: NativeFirebaseFirestoreWrapper.() -> NativeFirebaseFirestore +){ + + internal actual constructor(native: NativeFirebaseFirestore) : this({ native }) + internal constructor(app: FirebaseApp) : this( + { + NativeFirebaseFirestore( + initializeFirestore(app, settings.js).also { + emulatorSettings?.run { + connectFirestoreEmulator(it, host, port) + } + } + ) + } + ) + + private data class EmulatorSettings(val host: String, val port: Int) + + actual var settings: FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().build() + set(value) { + if (lazyNative.isInitialized()) { + throw IllegalStateException("FirebaseFirestore has already been started and its settings can no longer be changed. You can only call setFirestoreSettings() before calling any other methods on a FirebaseFirestore object.") + } else { + field = value + } + } + private var emulatorSettings: EmulatorSettings? = null + + // initializeFirestore must be called before any call, including before `getFirestore()` + // To allow settings to be updated, we defer creating the wrapper until the first call to `native` + private val lazyNative = lazy { + createNative() + } + actual val native: NativeFirebaseFirestore by lazyNative + private val js get() = native.js + + actual fun collection(collectionPath: String) = rethrow { + NativeCollectionReference( + dev.gitlive.firebase.firestore.externals.collection( + js, + collectionPath + ) + ) + } + + actual fun collectionGroup(collectionId: String) = rethrow { + NativeQuery( + dev.gitlive.firebase.firestore.externals.collectionGroup( + js, + collectionId + ) + ) + } + + actual fun document(documentPath: String) = rethrow { + NativeDocumentReference( + doc( + js, + documentPath + ) + ) + } + + actual fun batch() = rethrow { NativeWriteBatch(writeBatch(js)) } + + actual fun setLoggingEnabled(loggingEnabled: Boolean) = + rethrow { setLogLevel(if (loggingEnabled) "error" else "silent") } + + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = + rethrow { dev.gitlive.firebase.firestore.externals.runTransaction( + js, + { GlobalScope.promise { NativeTransaction(it).func() } }).await() } + + actual suspend fun clearPersistence() = + rethrow { clearIndexedDbPersistence(js).await() } + + actual fun useEmulator(host: String, port: Int) = rethrow { + settings = firestoreSettings(settings) { + this.host = "$host:$port" + } + emulatorSettings = EmulatorSettings(host, port) + } + + actual suspend fun disableNetwork() { + rethrow { dev.gitlive.firebase.firestore.externals.disableNetwork(js).await() } + } + + actual suspend fun enableNetwork() { + rethrow { dev.gitlive.firebase.firestore.externals.enableNetwork(js).await() } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..61c98df06 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,154 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.WhereConstraint +import dev.gitlive.firebase.firestore.errorToException +import dev.gitlive.firebase.firestore.externals.Query +import dev.gitlive.firebase.firestore.externals.QueryConstraint +import dev.gitlive.firebase.firestore.externals.and +import dev.gitlive.firebase.firestore.externals.getDocs +import dev.gitlive.firebase.firestore.externals.getDocsFromCache +import dev.gitlive.firebase.firestore.externals.getDocsFromServer +import dev.gitlive.firebase.firestore.externals.onSnapshot +import dev.gitlive.firebase.firestore.externals.or +import dev.gitlive.firebase.firestore.externals.query +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.firestore.wrapped +import kotlinx.coroutines.await +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlin.js.json + +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { + + constructor(js: Query) : this(NativeQuery(js)) + + open val js: Query get() = native.js + + actual suspend fun get(source: Source) = rethrow { QuerySnapshot(js.get(source).await()) } + + actual fun limit(limit: Number) = query( + js, + dev.gitlive.firebase.firestore.externals.limit(limit) + ).wrapped + + actual fun where(filter: Filter) = query(js, filter.toQueryConstraint()).wrapped + + private fun Filter.toQueryConstraint(): QueryConstraint = when (this) { + is Filter.And -> and(*filters.map { it.toQueryConstraint() }.toTypedArray()) + is Filter.Or -> or(*filters.map { it.toQueryConstraint() }.toTypedArray()) + is Filter.Field -> { + val value = when (constraint) { + is WhereConstraint.ForNullableObject -> constraint.safeValue + is WhereConstraint.ForObject -> constraint.safeValue + is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() + } + dev.gitlive.firebase.firestore.externals.where(field, constraint.filterOp, value) + } + is Filter.Path -> { + val value = when (constraint) { + is WhereConstraint.ForNullableObject -> constraint.safeValue + is WhereConstraint.ForObject -> constraint.safeValue + is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() + } + dev.gitlive.firebase.firestore.externals.where(path.js, constraint.filterOp, value) + } + } + + private val WhereConstraint.filterOp: String get() = when (this) { + is WhereConstraint.EqualTo -> "==" + is WhereConstraint.NotEqualTo -> "!=" + is WhereConstraint.LessThan -> "<" + is WhereConstraint.LessThanOrEqualTo -> "<=" + is WhereConstraint.GreaterThan -> ">" + is WhereConstraint.GreaterThanOrEqualTo -> ">=" + is WhereConstraint.ArrayContains -> "array-contains" + is WhereConstraint.ArrayContainsAny -> "array-contains-any" + is WhereConstraint.InArray -> "in" + is WhereConstraint.NotInArray -> "not-in" + } + + actual fun orderBy(field: String, direction: Direction) = rethrow { + query(js, dev.gitlive.firebase.firestore.externals.orderBy(field, direction.jsString)).wrapped + } + + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = rethrow { + query(js, dev.gitlive.firebase.firestore.externals.orderBy(field, direction.jsString)).wrapped + } + + actual fun startAfter(document: NativeDocumentSnapshot) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.startAfter(document.js) + ).wrapped } + + actual fun startAfter(vararg fieldValues: Any) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.startAfter(*fieldValues) + ).wrapped } + + actual fun startAt(document: NativeDocumentSnapshot) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.startAt(document.js) + ).wrapped } + + actual fun startAt(vararg fieldValues: Any) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.startAt(*fieldValues) + ).wrapped } + + actual fun endBefore(document: NativeDocumentSnapshot) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.endBefore(document.js) + ).wrapped } + + actual fun endBefore(vararg fieldValues: Any) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.endBefore(*fieldValues) + ).wrapped } + + actual fun endAt(document: NativeDocumentSnapshot) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.endAt(document.js) + ).wrapped } + + actual fun endAt(vararg fieldValues: Any) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.endAt(*fieldValues) + ).wrapped } + + actual val snapshots get() = callbackFlow { + val unsubscribe = rethrow { + onSnapshot( + js, + { trySend(QuerySnapshot(it)) }, + { close(errorToException(it)) } + ) + } + awaitClose { rethrow { unsubscribe() } } + } + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val unsubscribe = rethrow { + onSnapshot( + js, + json("includeMetadataChanges" to includeMetadataChanges), + { trySend(QuerySnapshot(it)) }, + { close(errorToException(it)) } + ) + } + awaitClose { rethrow { unsubscribe() } } + } +} + +private fun Query.get(source: Source) = when (source) { + Source.DEFAULT -> getDocs(this) + Source.CACHE -> getDocsFromCache(this) + Source.SERVER -> getDocsFromServer(this) +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..9bcabf42d --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,57 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.externals.Transaction +import dev.gitlive.firebase.firestore.js +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await + +@PublishedApi +internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { + + constructor(js: Transaction) : this(NativeTransaction(js)) + + val js = native.js + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeTransactionWrapper = rethrow { + js.set(documentRef.js, encodedData.js, setOptions.js) + } + .let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.js) } + .let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeTransactionWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeTransactionWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + rethrow { js.delete(documentRef.js) } + .let { this } + + actual suspend fun get(documentRef: DocumentReference) = + rethrow { NativeDocumentSnapshotWrapper(js.get(documentRef.js).await()) } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..819e563d1 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,53 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.externals.WriteBatch +import dev.gitlive.firebase.firestore.js +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await + +@PublishedApi +internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { + + constructor(js: WriteBatch) : this(NativeWriteBatch(js)) + + val js = native.js + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.js, setOptions.js) }.let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.js) } + .let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeWriteBatchWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeWriteBatchWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + rethrow { js.delete(documentRef.js) } + .let { this } + + actual suspend fun commit() = rethrow { js.commit().await() } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt new file mode 100644 index 000000000..3857ad03a --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt @@ -0,0 +1,12 @@ +package dev.gitlive.firebase.firestore.internal + +import kotlin.js.Json +import kotlin.js.json + +internal val SetOptions.js: Json + get() = when (this) { + is SetOptions.Merge -> json("merge" to true) + is SetOptions.Overwrite -> json("merge" to false) + is SetOptions.MergeFields -> json("mergeFields" to fields.toTypedArray()) + is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) +}