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-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..1e5fc20a8 --- /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() = getRaw() + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> 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 97% 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 59ac0c431..d4f8316fc 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,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-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-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..9c070b82d --- /dev/null +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -0,0 +1,29 @@ +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 + +internal fun EncodedObject.getRaw(): Map = when (this) { + is EncodedObjectImpl -> raw +} + +@JvmInline +@PublishedApi +internal value class EncodedObjectImpl(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/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 78% 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 0497ec0d7..0015e3cfd 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,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.EncodeSettings import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.SerialDescriptor @@ -11,13 +12,14 @@ import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule + @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 } 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? = @@ -29,7 +31,33 @@ 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]. + * 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") + } + 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") + } + 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 internal inline fun encode(value: T, encodeSettings: EncodeSettings): Any? = value?.let { @@ -60,7 +88,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 80% 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 5fdeaf72c..1e2385846 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,18 +2,24 @@ * 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 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.assertFailsWith +import kotlin.test.assertNull @Serializable object TestObject { @@ -71,6 +77,17 @@ data class NestedClass( class EncodersTest { + @Test + 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() { val list = listOf("One", "Two", "Three") @@ -82,6 +99,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 +121,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 } @@ -380,4 +419,70 @@ class EncodersTest { reencoded ) } + + @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 } + + 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).getRaw()) + + assertEquals(emptyMap(), encodeAsObject(TestObject).getRaw()) + + 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) + assertEquals(value, decode(encoded)) + + val nullableEncoded = encode(null) + assertNull(nullableEncoded) + assertNull(decode(nullableEncoded)) + } } 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..9d4e6643a --- /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() = getRaw().mapKeys { (key, _) -> key } + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> 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 97% 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 144b0e60f..966a7e64e 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,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-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..47f3aad5e --- /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(*getRaw().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/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 97% 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 dbb3d1f15..5cf22b6bd 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,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/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/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-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/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/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index 3980e67f0..c94b39905 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 @@ -14,11 +14,13 @@ 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.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.reencodeTransformation +import dev.gitlive.firebase.internal.android +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 @@ -93,6 +95,10 @@ actual class FirebaseDatabase internal constructor(val android: com.google.fireb 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( @@ -207,9 +213,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 } @@ -255,7 +260,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 @@ -299,8 +303,8 @@ internal actual class NativeOnDisconnect internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun updateEncodedChildren(encodedUpdate: Map) = - android.updateChildren(encodedUpdate) + 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/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/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 7bbd44935..8672bd8a4 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,13 +7,15 @@ package dev.gitlive.firebase.database import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeDecodeSettingsBuilder import dev.gitlive.firebase.EncodeSettings +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp 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.internal.encode +import dev.gitlive.firebase.internal.encodeAsObject import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer @@ -39,6 +41,10 @@ expect class FirebaseDatabase { fun setPersistenceEnabled(enabled: Boolean) fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) fun useEmulator(host: String, port: Int) + + fun goOffline() + + fun goOnline() } data class ChildEvent internal constructor( @@ -80,7 +86,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 @@ -116,7 +122,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() @@ -142,7 +148,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: EncodedObject) } class OnDisconnect internal constructor(@PublishedApi internal val native: NativeOnDisconnect) { @@ -158,7 +164,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(update.mapValues { (_, it) -> encode(it, 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/commonTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt index ba3f7be59..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 @@ -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,12 +17,15 @@ 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 expect val emulatorHost: String expect val context: Any expect annotation class IgnoreForAndroidUnitTest() +expect annotation class IgnoreForAndroidTest() @IgnoreForAndroidUnitTest class FirebaseDatabaseTest { @@ -41,9 +45,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" ) ) @@ -84,7 +88,7 @@ class FirebaseDatabaseTest { .valueEvents .first() - val firebaseDatabaseChildCount = dataSnapshot.children.count() + val firebaseDatabaseChildCount = dataSnapshot.child("values").children.count() assertEquals(3, firebaseDatabaseChildCount) } @@ -168,6 +172,49 @@ class FirebaseDatabaseTest { 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) + } + + // 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() + val reference = database + .reference("FirebaseRealtimeDatabaseTest") + 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() + + database.goOnline() + ensureDatabaseConnected() + assertFalse(valueEvents.first().exists) + } + private suspend fun setupRealtimeData() { ensureDatabaseConnected() val firebaseDatabaseTestReference = database @@ -177,9 +224,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 92de88a7c..bc8d1d489 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 @@ -23,8 +23,10 @@ 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.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 import kotlinx.coroutines.channels.awaitClose @@ -76,6 +78,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) { @@ -171,9 +177,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() { @@ -202,7 +207,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 @@ -244,9 +248,8 @@ internal actual class NativeOnDisconnect internal constructor( ios.await(persistenceEnabled) { onDisconnectSetValue(encodedValue, it) } } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateEncodedChildren(encodedUpdate: Map) { - 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/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/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index dfb187d8e..7dfc71a81 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 @@ -27,8 +27,10 @@ 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.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 import kotlinx.coroutines.coroutineScope @@ -46,6 +48,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 @@ -74,6 +78,10 @@ actual class FirebaseDatabase internal constructor(val js: Database) { actual fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) {} 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( @@ -157,7 +165,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 @@ -178,8 +185,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.js).awaitWhileOnline(database) } actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { @@ -230,8 +237,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: EncodedObject) = + rethrow { js.update(encodedUpdate.js).awaitWhileOnline(database) } } 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/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/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/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index eb73173e5..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,26 +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 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 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 @@ -53,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) = NativeCollectionReference(native.collection(collectionPath)) - - actual fun collectionGroup(collectionId: String) = native.collectionGroup(collectionId).native - - actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - - actual fun batch() = NativeWriteBatch(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 { NativeTransaction(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 @@ -174,305 +87,29 @@ 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) -} - -@PublishedApi -internal actual class NativeWriteBatch(val android: com.google.firebase.firestore.WriteBatch) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): NativeWriteBatch = (setOptions.android?.let { - android.set(documentRef.android, encodedData, it) - } ?: android.set(documentRef.android, encodedData)).let { - this - } - - @Suppress("UNCHECKED_CAST") - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any) = android.update(documentRef.android, encodedData as Map).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.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) - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } - - actual suspend fun commit() { - android.commit().await() - } -} - -val WriteBatch.android get() = native.android - -@PublishedApi -internal actual class NativeTransaction(val android: com.google.firebase.firestore.Transaction) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): NativeTransaction { - 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 typealias NativeWriteBatch = com.google.firebase.firestore.WriteBatch - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } +val WriteBatch.android get() = native - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } +actual typealias NativeTransaction = com.google.firebase.firestore.Transaction - actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } - - actual suspend fun get(documentRef: DocumentReference) = - NativeDocumentSnapshot(android.get(documentRef.android)) -} - -val Transaction.android get() = native.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: NativeCollectionReference - get() = NativeCollectionReference(android.parent) - - actual fun collection(collectionPath: String) = NativeCollectionReference(android.collection(collectionPath)) - - actual suspend fun get(source: Source) = - NativeDocumentSnapshot(android.get(source.toAndroidSource()).await()) - - actual suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) { - val task = (setOptions.android?.let { - android.set(encodedData, it) - } ?: android.set(encodedData)) - task.await() - } - - @Suppress("UNCHECKED_CAST") - actual suspend fun updateEncoded(encodedData: Any) { - android.update(encodedData as Map).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(NativeDocumentSnapshot(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 -@PublishedApi -internal actual open class NativeQuery(open val android: AndroidQuery) -internal val AndroidQuery.native get() = NativeQuery(this) +actual typealias NativeQuery = AndroidQuery -actual open class Query internal actual constructor(nativeQuery: NativeQuery) { - - open val android = nativeQuery.android - - actual suspend fun get(source: Source) = QuerySnapshot(android.get(source.toAndroidSource()).await()) - - actual fun limit(limit: Number) = Query(NativeQuery(android.limit(limit.toLong()))) - - actual val snapshots get() = addSnapshotListener { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - - internal actual fun where(filter: Filter) = Query( - android.where(filter.toAndroidFilter()).native - ) - - 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) - } - } - } - } - - 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) - - 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) - - 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) - - private fun addSnapshotListener( - includeMetadataChanges: Boolean = false, - listener: ProducerScope.(com.google.firebase.firestore.QuerySnapshot?, 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 Query.android get() = native 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) { - - actual val path: String - get() = android.path - - actual val document: NativeDocumentReference - get() = NativeDocumentReference(android.document()) - - actual val parent: NativeDocumentReference? - get() = android.parent?.let{ NativeDocumentReference(it) } - - actual fun document(documentPath: String) = NativeDocumentReference(android.document(documentPath)) - - actual suspend fun addEncoded(data: Any) = NativeDocumentReference(android.add(data).await()) -} +actual typealias NativeCollectionReference = com.google.firebase.firestore.CollectionReference -val CollectionReference.android get() = native.android +val CollectionReference.android get() = native actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException @@ -482,7 +119,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) @@ -490,7 +127,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 @@ -499,29 +136,9 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC get() = android.type } -@PublishedApi -internal actual class NativeDocumentSnapshot(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 encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = android.getData(serverTimestampBehavior.toAndroid()) - - actual fun contains(field: String) = android.contains(field) - - actual val exists get() = android.exists() - - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.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 - } -} +actual typealias NativeDocumentSnapshot = com.google.firebase.firestore.DocumentSnapshot -val DocumentSnapshot.android get() = native.android +val DocumentSnapshot.android get() = native actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { actual val hasPendingWrites: Boolean get() = android.hasPendingWrites() @@ -550,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 0f2f8fe30..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,7 +1,8 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.firestore.internal.NativeDocumentReference +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/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/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 6afe7dd7a..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 @@ -4,7 +4,23 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeSettings +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 import kotlinx.coroutines.flow.map import kotlinx.serialization.DeserializationStrategy @@ -20,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)) @@ -110,79 +110,65 @@ 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 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 -} +data class Transaction internal constructor(@PublishedApi internal val nativeWrapper: NativeTransactionWrapper) { + + constructor(native: NativeTransaction) : this(NativeTransactionWrapper(native)) -data class Transaction internal constructor(@PublishedApi internal val native: NativeTransaction) { + 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()) @@ -190,44 +176,46 @@ 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 -internal expect open class NativeQuery +expect open class NativeQuery -expect open class Query internal constructor(nativeQuery: NativeQuery) { - fun limit(limit: Number): Query - val snapshots: Flow - fun snapshots(includeMetadataChanges: Boolean = false): Flow - suspend fun get(source: Source = Source.DEFAULT): QuerySnapshot +open class Query internal constructor(internal val nativeQuery: NativeQueryWrapper) { - internal fun where(filter: Filter): Query + constructor(native: NativeQuery) : this(NativeQueryWrapper(native)) - internal fun _orderBy(field: String, direction: Direction): Query - internal fun _orderBy(field: FieldPath, direction: Direction): Query + open val native = nativeQuery.native - 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 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(source: Source = Source.DEFAULT): QuerySnapshot = nativeQuery.get(source) - 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 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 Query.where(builder: FilterBuilder.() -> Filter?) = builder(FilterBuilder())?.let { where(it) } ?: this + 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 { @@ -281,98 +269,72 @@ 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 - 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 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 - suspend fun commit() -} +data class WriteBatch internal constructor(@PublishedApi internal val nativeWrapper: NativeWriteBatchWrapper) { + + constructor(native: NativeWriteBatch) : this(NativeWriteBatchWrapper(native)) -data class WriteBatch internal constructor(@PublishedApi internal val native: NativeWriteBatch) { + 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) { + 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(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) { + 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()) @@ -380,39 +342,21 @@ 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(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. */ expect class NativeDocumentReferenceType -@PublishedApi -internal expect class NativeDocumentReference(nativeValue: NativeDocumentReferenceType) { - 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(source: Source = Source.DEFAULT): NativeDocumentSnapshot - suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) - suspend fun updateEncoded(encodedData: Any) - 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) { @@ -429,55 +373,60 @@ data class DocumentReference internal constructor(@PublishedApi internal val nat suspend fun get(source: Source = Source.DEFAULT): DocumentSnapshot = DocumentSnapshot(native.get(source)) @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()) @@ -488,42 +437,38 @@ data class DocumentReference internal constructor(@PublishedApi internal val nat suspend fun delete() = native.delete() } -@PublishedApi -internal expect class NativeCollectionReference : NativeQuery { - val path: String - val document: NativeDocumentReference - val parent: NativeDocumentReference? +expect class NativeCollectionReference : NativeQuery - fun document(documentPath: String): NativeDocumentReference - suspend fun addEncoded(data: Any): NativeDocumentReference -} +data class CollectionReference internal constructor(@PublishedApi internal val nativeWrapper: NativeCollectionReferenceWrapper) : Query(nativeWrapper) { -data class CollectionReference internal constructor(@PublishedApi internal val native: NativeCollectionReference) : Query(native) { + constructor(native: NativeCollectionReference) : this(NativeCollectionReferenceWrapper(native)) - val path: String get() = native.path - val document: DocumentReference get() = DocumentReference(native.document) - val parent: DocumentReference? get() = native.parent?.let(::DocumentReference) + override 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(native.document(documentPath)) + 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 @@ -575,39 +520,40 @@ expect class DocumentChange { val type: ChangeType } -@PublishedApi -internal expect class NativeDocumentSnapshot { - - val exists: Boolean - val id: String - val reference: NativeDocumentReference - val metadata: SnapshotMetadata +expect class NativeDocumentSnapshot - fun contains(field: String): Boolean +data class DocumentSnapshot internal constructor(@PublishedApi internal val nativeWrapper: NativeDocumentSnapshotWrapper) { - fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? -} + constructor(native: NativeDocumentSnapshot) : this(NativeDocumentSnapshotWrapper(native)) -data class DocumentSnapshot internal constructor(@PublishedApi internal val native: NativeDocumentSnapshot) { + val native = nativeWrapper.native - 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 + 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.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) @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.encoded, 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/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/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/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/FirestoreSourceTest.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt index 8d849900b..d59ce53c2 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt @@ -62,7 +62,7 @@ class FirestoreSourceTest { setDoc() val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.SERVER) assertTrue(doc.exists) - assertFalse(doc.native.metadata.isFromCache) + assertFalse(doc.metadata.isFromCache) } @Test @@ -71,7 +71,7 @@ class FirestoreSourceTest { setDoc() val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.SERVER) assertTrue(doc.exists) - assertFalse(doc.native.metadata.isFromCache) + assertFalse(doc.metadata.isFromCache) } @Test @@ -83,7 +83,7 @@ class FirestoreSourceTest { val cachedDoc = firestore.collection("testFirestoreQuerying").document("one").get(Source.CACHE) assertTrue(cachedDoc.exists) - assertTrue(cachedDoc.native.metadata.isFromCache) + assertTrue(cachedDoc.metadata.isFromCache) } @Test @@ -100,14 +100,14 @@ class FirestoreSourceTest { initializeFirebase(persistenceEnabled = false) val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.DEFAULT) assertTrue(doc.exists) - assertFalse(doc.native.metadata.isFromCache) + assertFalse(doc.metadata.isFromCache) } @Test fun testGet() = runTest { initializeFirebase(persistenceEnabled = false) val doc = firestore.collection("testFirestoreQuerying").document("one").get() assertTrue(doc.exists) - assertFalse(doc.native.metadata.isFromCache) + assertFalse(doc.metadata.isFromCache) } @Test @@ -117,7 +117,7 @@ class FirestoreSourceTest { val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.DEFAULT) assertTrue(doc.exists) // Firebase defaults to first fetching from server - assertFalse(doc.native.metadata.isFromCache) + assertFalse(doc.metadata.isFromCache) } } \ No newline at end of file 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 4928a36cb..1e2aa707b 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 266f77e35..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 @@ -6,14 +6,13 @@ package dev.gitlive.firebase.firestore import cocoapods.FirebaseFirestoreInternal.* import cocoapods.FirebaseFirestoreInternal.FIRDocumentChangeType.* -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +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 @@ -38,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) = NativeCollectionReference(native.collectionWithPath(collectionPath)) - - actual fun collectionGroup(collectionId: String) = native.collectionGroupWithID(collectionId).native - - actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - - actual fun batch() = NativeWriteBatch(native.batch()) - - actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = - FIRFirestore.enableLogging(loggingEnabled) - - actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = - awaitResult { native.runTransactionWithBlock({ transaction, _ -> runBlocking { NativeTransaction(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( @@ -144,254 +97,26 @@ actual fun firestoreSettings( } }.apply(builder).build() -@Suppress("UNCHECKED_CAST") -@PublishedApi -internal actual class NativeWriteBatch(val ios: FIRWriteBatch) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - 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) - }.let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatch = ios.updateData(encodedData as Map, documentRef.ios).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatch = ios.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatch = ios.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun delete(documentRef: DocumentReference) = - ios.deleteDocument(documentRef.ios).let { this } - - actual suspend fun commit() = await { ios.commitWithCompletion(it) } -} +actual typealias NativeWriteBatch = FIRWriteBatch -val WriteBatch.ios get() = native.ios - -@Suppress("UNCHECKED_CAST") -@PublishedApi -internal actual class NativeTransaction(val ios: FIRTransaction) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - 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) - }.let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransaction = ios.updateData(encodedData as Map, documentRef.ios).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransaction = ios.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransaction = ios.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun delete(documentRef: DocumentReference) = - ios.deleteDocument(documentRef.ios).let { this } - - actual suspend fun get(documentRef: DocumentReference) = - throwError { NativeDocumentSnapshot(ios.getDocument(documentRef.ios, it)!!) } +val WriteBatch.ios get() = native -} +actual typealias NativeTransaction = FIRTransaction -val Transaction.ios get() = native.ios +val Transaction.ios get() = 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)) } - 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: NativeCollectionReference - get() = NativeCollectionReference(ios.parent) - - - actual fun collection(collectionPath: String) = NativeCollectionReference(ios.collectionWithPath(collectionPath)) - - actual suspend fun get(source: Source) = - NativeDocumentSnapshot(awaitResult { ios.getDocumentWithSource(source.toIosSource(), it) }) - - actual suspend fun setEncoded(encodedData: Any, 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) - } - } - - actual suspend fun updateEncoded(encodedData: Any) = await { - ios.updateData(encodedData as Map, 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(NativeDocumentSnapshot(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 -@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 suspend fun get(source: Source) = QuerySnapshot(awaitResult { ios.getDocumentsWithSource(source.toIosSource(),it) }) - - actual fun limit(limit: Number) = Query(ios.queryLimitedTo(limit.toLong()).native) - - actual val snapshots get() = callbackFlow { - val listener = ios.addSnapshotListener { snapshot, error -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = ios.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 - ) - - 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) - } - } - - 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) - - 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 typealias NativeQuery = FIRQuery -@Suppress("UNCHECKED_CAST") -@PublishedApi -internal actual class NativeCollectionReference(override val ios: FIRCollectionReference) : NativeQuery(ios) { +val Query.ios get() = native - actual val path: String - get() = ios.path +actual typealias NativeCollectionReference = FIRCollectionReference - actual val document get() = NativeDocumentReference(ios.documentWithAutoID()) - - actual val parent get() = ios.parent?.let{ NativeDocumentReference(it) } - - actual fun document(documentPath: String) = NativeDocumentReference(ios.documentWithPath(documentPath)) - - actual suspend fun addEncoded(data: Any) = NativeDocumentReference(await { ios.addDocumentWithData(data as Map, it) }) -} - -val CollectionReference.ios get() = native.ios +val CollectionReference.ios get() = native actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) @@ -454,7 +179,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) @@ -462,7 +187,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 @@ -471,36 +196,9 @@ actual class DocumentChange(val ios: FIRDocumentChange) { get() = ChangeType.values().first { it.ios == ios.type } } -@PublishedApi -internal actual class NativeDocumentSnapshot(val ios: FIRDocumentSnapshot) { - - actual val id get() = ios.documentID - - actual val reference get() = NativeDocumentReference(ios.reference) - - actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = - ios.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - - actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = - ios.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) - ?.mapValues { (_, value) -> - value?.takeIf { it !is NSNull } - } - - actual fun contains(field: String) = ios.valueForField(field) != null - - actual val exists get() = ios.exists +actual typealias NativeDocumentSnapshot = FIRDocumentSnapshot - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(ios.metadata) - - fun ServerTimestampBehavior.toIos() : FIRServerTimestampBehavior = when (this) { - ServerTimestampBehavior.ESTIMATE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorEstimate - ServerTimestampBehavior.NONE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorNone - ServerTimestampBehavior.PREVIOUS -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorPrevious - } -} - -val DocumentSnapshot.ios get() = native.ios +val DocumentSnapshot.ios get() = native actual class SnapshotMetadata(val ios: FIRSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = ios.pendingWrites @@ -521,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 -> @@ -557,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/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-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 4600481aa..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,41 +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 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 @@ -56,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())) } @@ -78,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( @@ -213,277 +111,25 @@ 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()) -} - -@PublishedApi -internal actual class NativeWriteBatch(val js: JsWriteBatch) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): NativeWriteBatch = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatch = rethrow { js.update(documentRef.js, encodedData) } - .let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatch = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatch = 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() } -} +actual data class NativeWriteBatch(val js: JsWriteBatch) val WriteBatch.js get() = native.js -@PublishedApi -internal actual class NativeTransaction(val js: JsTransaction) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): NativeTransaction = rethrow { - js.set(documentRef.js, encodedData, setOptions.js) - } - .let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransaction = rethrow { js.update(documentRef.js, encodedData) } - .let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransaction = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransaction = 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 { NativeDocumentSnapshot(js.get(documentRef.js).await()) } -} +actual data class NativeTransaction(val js: JsTransaction) 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: NativeCollectionReference - get() = rethrow { NativeCollectionReference(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: Any, setOptions: SetOptions) = rethrow { - setDoc(js, encodedData, setOptions.js).await() - } - - actual suspend fun updateEncoded(encodedData: Any) = rethrow { jsUpdate(js, encodedData).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 -@PublishedApi -internal actual open class NativeQuery(open val js: JsQuery) - -actual open class Query internal actual constructor(nativeQuery: NativeQuery) { - - constructor(js: JsQuery) : this(NativeQuery(js)) - - open val js: JsQuery = nativeQuery.js - - actual suspend fun get(source: Source) = rethrow { QuerySnapshot(js.get(source).await()) } - - actual fun limit(limit: Number) = Query(query(js, jsLimit(limit))) - - internal actual fun where(filter: Filter): Query = Query( - query(js, filter.toQueryConstraint()) - ) - - 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" - } - - internal actual fun _orderBy(field: String, direction: Direction) = rethrow { - Query(query(js, orderBy(field, direction.jsString))) - } - - internal actual fun _orderBy(field: FieldPath, direction: Direction) = rethrow { - Query(query(js, orderBy(field.js, direction.jsString))) - } - - internal actual fun _startAfter(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAfter(document.js))) } - - internal actual fun _startAfter(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAfter(*fieldValues))) } - - internal actual fun _startAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAt(document.js))) } - - internal actual fun _startAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAt(*fieldValues))) } - - internal actual fun _endBefore(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndBefore(document.js))) } - - internal actual fun _endBefore(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndBefore(*fieldValues))) } +actual open class NativeQuery(open val js: JsQuery) +internal val JsQuery.wrapped get() = NativeQuery(this) - internal actual fun _endAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndAt(document.js))) } +val Query.js get() = native.js - internal actual fun _endAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndAt(*fieldValues))) } - - 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() } } - } -} - -@PublishedApi -internal actual class NativeCollectionReference(override val js: JsCollectionReference) : NativeQuery(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: Any) = rethrow { - NativeDocumentReference(addDoc(js, data).await()) - } -} +actual data class NativeCollectionReference(override val js: JsCollectionReference) : NativeQuery(js) val CollectionReference.js get() = native.js @@ -494,7 +140,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) @@ -502,7 +148,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 @@ -511,27 +157,7 @@ actual class DocumentChange(val js: JsDocumentChange) { get() = ChangeType.values().first { it.jsString == js.type } } -@PublishedApi -internal actual class NativeDocumentSnapshot(val js: JsDocumentSnapshot) { - - 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 encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { - js.data(getTimestampsOptions(serverTimestampBehavior)) - } - - actual fun contains(field: String) = rethrow { js.get(field) != 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()) -} +actual data class NativeDocumentSnapshot(val js: JsDocumentSnapshot) val DocumentSnapshot.js get() = native.js @@ -557,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, @@ -645,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()) +} 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..adc37e244 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 @@ -64,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 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" } } 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 52ff28a51..cfddc6de9 100644 --- a/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -12,5 +12,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)) } 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