From 816bd6241503e49744b3b7884ab5f5c66acc9960 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Tue, 19 Dec 2023 20:59:15 +0100 Subject: [PATCH 01/27] Adding Serialization fixes --- .../kotlin/dev/gitlive/firebase/_decoders.kt | 58 +-- .../kotlin/dev/gitlive/firebase/_encoders.kt | 23 +- .../gitlive/firebase/EncodeDecodeSettings.kt | 33 ++ .../dev/gitlive/firebase/Polymorphic.kt | 11 +- .../kotlin/dev/gitlive/firebase/decoders.kt | 101 ++++-- .../kotlin/dev/gitlive/firebase/encoders.kt | 65 +++- .../dev/gitlive/firebase/serializers.kt | 12 +- .../dev/gitlive/firebase/EncodersTest.kt | 208 ++++++----- .../kotlin/dev/gitlive/firebase/_decoders.kt | 48 +-- .../kotlin/dev/gitlive/firebase/_encoders.kt | 27 +- .../kotlin/dev/gitlive/firebase/_decoders.kt | 56 +-- .../kotlin/dev/gitlive/firebase/_encoders.kt | 37 +- .../dev/gitlive/firebase/database/database.kt | 66 ++-- .../dev/gitlive/firebase/database/database.kt | 18 +- .../dev/gitlive/firebase/database/database.kt | 37 +- .../dev/gitlive/firebase/database/database.kt | 41 +-- .../dev/gitlive/firebase/firestore/Ignore.kt | 6 + .../gitlive/firebase/firestore/firestore.kt | 4 +- .../gitlive/firebase/firestore/FieldValue.kt | 26 ++ .../gitlive/firebase/firestore/_encoders.kt | 10 + .../gitlive/firebase/firestore/firestore.kt | 341 ++++++++---------- .../dev/gitlive/firebase/firestore/Ignore.kt | 7 + .../gitlive/firebase/firestore/firestore.kt | 10 +- .../firebase/firestore/DeferredExtensions.kt | 17 + .../firestore/DocumentReferenceSerializer.kt | 20 + .../gitlive/firebase/firestore/FieldValue.kt | 17 + .../firestore/FieldValueSerializer.kt | 15 + .../gitlive/firebase/firestore/GeoPoint.kt | 15 - .../firebase/firestore/GeoPointSerializer.kt | 17 + .../gitlive/firebase/firestore/Timestamp.kt | 70 ---- .../firebase/firestore/TimestampSerializer.kt | 71 ++++ .../gitlive/firebase/firestore/encoders.kt | 15 + .../gitlive/firebase/firestore/firestore.kt | 244 ++++++++----- .../dev/gitlive/firebase/firestore/helpers.kt | 39 +- .../gitlive/firebase/firestore/serializers.kt | 28 ++ .../firestore/DeferredExtensionsTest.kt | 42 +++ .../firebase/firestore/FieldValueTests.kt | 6 + .../firebase/firestore/FirestoreAsync.kt | 84 +++++ .../firebase/firestore/GeoPointTests.kt | 23 +- .../dev/gitlive/firebase/firestore/Ignore.kt | 5 + .../gitlive/firebase/firestore/firestore.kt | 277 +++++++++++++- .../gitlive/firebase/firestore/FieldValue.kt | 27 ++ .../gitlive/firebase/firestore/_encoders.kt | 12 + .../gitlive/firebase/firestore/firestore.kt | 320 ++++++++-------- .../dev/gitlive/firebase/firestore/Ignore.kt | 6 + .../gitlive/firebase/firestore/firestore.kt | 5 +- .../gitlive/firebase/firestore/FieldValue.kt | 32 ++ .../gitlive/firebase/firestore/GeoPoint.kt | 1 - .../gitlive/firebase/firestore/_encoders.kt | 10 + .../gitlive/firebase/firestore/firestore.kt | 326 ++++++++--------- .../dev/gitlive/firebase/firestore/Ignore.kt | 7 + .../gitlive/firebase/firestore/firestore.kt | 12 +- .../dev/gitlive/firebase/firestore/Ignore.kt | 6 + .../gitlive/firebase/firestore/firestore.kt | 4 +- .../gitlive/firebase/functions/functions.kt | 16 +- .../gitlive/firebase/functions/functions.kt | 16 +- .../gitlive/firebase/functions/functions.kt | 16 +- .../gitlive/firebase/functions/functions.kt | 14 +- 58 files changed, 1980 insertions(+), 1100 deletions(-) create mode 100644 firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt create mode 100644 firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt create mode 100644 firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/serializers.kt create mode 100644 firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt create mode 100644 firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt create mode 100644 firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt create mode 100644 firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt create mode 100644 firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt create mode 100644 firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt index 4cd747022..5a33fa84c 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt @@ -4,37 +4,41 @@ package dev.gitlive.firebase -import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.encoding.CompositeDecoder -actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder = when(descriptor.kind) { - StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> (value as Map<*, *>).let { map -> - FirebaseClassDecoder(map.size, { map.containsKey(it) }) { desc, index -> - val elementName = desc.getElementName(index) - if (desc.kind is PolymorphicKind && elementName == "value") { - map - } else { - map[desc.getElementName(index)] - } - } - } - StructureKind.LIST -> - when(value) { - is List<*> -> value - is Map<*, *> -> value.asSequence() - .sortedBy { (it) -> it.toString().toIntOrNull() } - .map { (_, it) -> it } - .toList() - else -> error("unexpected type, got $value when expecting a list") - } - .let { FirebaseCompositeDecoder(it.size) { _, index -> it[index] } } - StructureKind.MAP -> (value as Map<*, *>).entries.toList().let { - FirebaseCompositeDecoder(it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } } - } - else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") +actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when (descriptor.kind) { + StructureKind.CLASS, StructureKind.OBJECT -> decodeAsMap(false) + StructureKind.LIST -> (value as? List<*>).orEmpty().let { + FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index] } } + StructureKind.MAP -> (value as? Map<*, *>).orEmpty().entries.toList().let { + FirebaseCompositeDecoder( + it.size, + settings + ) { _, index -> it[index / 2].run { if (index % 2 == 0) key else value } } + } + + is PolymorphicKind -> decodeAsMap(polymorphicIsNested) + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") +} + actual fun getPolymorphicType(value: Any?, discriminator: String): String = - (value as Map<*,*>)[discriminator] as String \ No newline at end of file + (value as? Map<*,*>).orEmpty()[discriminator] as String + +private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map -> + FirebaseClassDecoder(map.size, settings, { map.containsKey(it) }) { desc, index -> + if (isNestedPolymorphic) { + if (index == 0) + map[desc.getElementName(index)] + else { + map + } + } else { + map[desc.getElementName(index)] + } + } +} diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt index c34791203..59ac0c431 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -4,6 +4,7 @@ package dev.gitlive.firebase +import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set @@ -11,16 +12,22 @@ import kotlin.collections.set actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> mutableListOf() .also { value = it } - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault) { _, index, value -> it.add(index, value) } } + .let { FirebaseCompositeEncoder(settings) { _, index, value -> it.add(index, value) } } StructureKind.MAP -> mutableListOf() - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } } - StructureKind.CLASS, StructureKind.OBJECT -> mutableMapOf() - .also { value = it } - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, + .let { FirebaseCompositeEncoder(settings, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } } + StructureKind.CLASS, StructureKind.OBJECT -> encodeAsMap(descriptor) + is PolymorphicKind -> encodeAsMap(descriptor) + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") +} + +private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = mutableMapOf() + .also { value = it } + .let { + FirebaseCompositeEncoder( + settings, setPolymorphicType = { discriminator, type -> it[discriminator] = type }, set = { _, index, value -> it[descriptor.getElementName(index)] = value } - ) } - else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") -} \ No newline at end of file + ) + } diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt new file mode 100644 index 000000000..6a69db5a0 --- /dev/null +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt @@ -0,0 +1,33 @@ +package dev.gitlive.firebase + +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule + +/** + * Settings used to configure encoding/decoding + */ +sealed class EncodeDecodeSettings { + + /** + * The [SerializersModule] to use for serialization. This allows for polymorphic serialization on runtime + */ + abstract val serializersModule: SerializersModule +} + +/** + * [EncodeDecodeSettings] used when encoding an object + * @property shouldEncodeElementDefault 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( + val shouldEncodeElementDefault: Boolean = true, + override val serializersModule: SerializersModule = EmptySerializersModule(), +) : EncodeDecodeSettings() + +/** + * [EncodeDecodeSettings] used when decoding an object + * @param serializersModule the [SerializersModule] to use for deserialization. This allows for polymorphic serialization on runtime + */ +data class DecodeSettings( + override val serializersModule: SerializersModule = EmptySerializersModule(), +) : EncodeDecodeSettings() diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt index 58f1ed71b..979415c14 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt @@ -16,10 +16,16 @@ internal fun FirebaseEncoder.encodePolymorphically( value: T, ifPolymorphic: (String) -> Unit ) { + // If serializer is not an AbstractPolymorphicSerializer or if we are encoding this as a list, we can just use the regular serializer + // This will result in calling structureEncoder for complicated structures + // For PolymorphicKind this will first encode the polymorphic discriminator as a String and the remaining StructureKind.Class as a map of key-value pairs + // This will result in a list structured like: (type, { classKey = classValue }) if (serializer !is AbstractPolymorphicSerializer<*>) { serializer.serialize(this, value) return } + + // When doing Polymorphic Serialization with EncodeDecodeSettings.PolymorphicStructure.MAP we will use the polymorphic serializer of the class. val casted = serializer as AbstractPolymorphicSerializer val baseClassDiscriminator = serializer.descriptor.classDiscriminator() val actualSerializer = casted.findPolymorphicSerializer(this, value as Any) @@ -32,15 +38,15 @@ internal fun FirebaseDecoder.decodeSerializableValuePolymorphic( value: Any?, deserializer: DeserializationStrategy, ): T { + // If deserializer is not an AbstractPolymorphicSerializer or if we are decoding this from a list, we can just use the regular serializer if (deserializer !is AbstractPolymorphicSerializer<*>) { return deserializer.deserialize(this) } - val casted = deserializer as AbstractPolymorphicSerializer val discriminator = deserializer.descriptor.classDiscriminator() val type = getPolymorphicType(value, discriminator) val actualDeserializer = casted.findPolymorphicSerializerOrNull( - structureDecoder(deserializer.descriptor), + structureDecoder(deserializer.descriptor, false), type ) as DeserializationStrategy return actualDeserializer.deserialize(this) @@ -55,4 +61,3 @@ internal fun SerialDescriptor.classDiscriminator(): String { } return "type" } - diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt index f9501d342..a292c249d 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt @@ -15,25 +15,26 @@ import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer -@Suppress("UNCHECKED_CAST") -inline fun decode(value: Any?): T { +inline fun decode(value: Any?): T = decode(value, DecodeSettings()) +inline fun decode(value: Any?, settings: DecodeSettings): T { val strategy = serializer() - return decode(strategy as DeserializationStrategy, value) + return decode(strategy as DeserializationStrategy, value, settings) } - -fun decode(strategy: DeserializationStrategy, value: Any?): T { +fun decode(strategy: DeserializationStrategy, value: Any?): T = decode(strategy, value, DecodeSettings()) +fun decode(strategy: DeserializationStrategy, value: Any?, settings: DecodeSettings): T { require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" } - return FirebaseDecoder(value).decodeSerializableValue(strategy) + return FirebaseDecoder(value, settings).decodeSerializableValue(strategy) } -expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder +expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder expect fun getPolymorphicType(value: Any?, discriminator: String): String -class FirebaseDecoder(internal val value: Any?) : Decoder { +class FirebaseDecoder(val value: Any?, internal val settings: DecodeSettings) : Decoder { + + constructor(value: Any?) : this(value, DecodeSettings()) - override val serializersModule: SerializersModule - get() = EmptySerializersModule() + override val serializersModule: SerializersModule = settings.serializersModule - override fun beginStructure(descriptor: SerialDescriptor) = structureDecoder(descriptor) + override fun beginStructure(descriptor: SerialDescriptor) = structureDecoder(descriptor, true) override fun decodeString() = decodeString(value) @@ -59,7 +60,7 @@ class FirebaseDecoder(internal val value: Any?) : Decoder { override fun decodeNull() = decodeNull(value) - override fun decodeInline(descriptor: SerialDescriptor) = FirebaseDecoder(value) + override fun decodeInline(descriptor: SerialDescriptor) = FirebaseDecoder(value, settings) override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { return decodeSerializableValuePolymorphic(value, deserializer) @@ -68,26 +69,35 @@ class FirebaseDecoder(internal val value: Any?) : Decoder { class FirebaseClassDecoder( size: Int, + settings: DecodeSettings, private val containsKey: (name: String) -> Boolean, get: (descriptor: SerialDescriptor, index: Int) -> Any? -) : FirebaseCompositeDecoder(size, get) { +) : FirebaseCompositeDecoder(size, settings, get) { private var index: Int = 0 override fun decodeSequentially() = false - override fun decodeElementIndex(descriptor: SerialDescriptor): Int = - (index until descriptor.elementsCount) - .firstOrNull { !descriptor.isElementOptional(it) || containsKey(descriptor.getElementName(it)) } + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + return (index until descriptor.elementsCount) + .firstOrNull { + !descriptor.isElementOptional(it) || containsKey( + descriptor.getElementName( + it + ) + ) + } ?.also { index = it + 1 } ?: DECODE_DONE + } } open class FirebaseCompositeDecoder( private val size: Int, - private val get: (descriptor: SerialDescriptor, index: Int) -> Any? + internal val settings: DecodeSettings, + private val get: (descriptor: SerialDescriptor, index: Int) -> Any?, ): CompositeDecoder { - override val serializersModule = EmptySerializersModule() + override val serializersModule: SerializersModule = settings.serializersModule override fun decodeSequentially() = true @@ -100,21 +110,30 @@ open class FirebaseCompositeDecoder( index: Int, deserializer: DeserializationStrategy, previousValue: T? - ) = deserializer.deserialize(FirebaseDecoder(get(descriptor, index))) + ) = decodeElement(descriptor, index) { + deserializer.deserialize(FirebaseDecoder(it, settings)) + } - override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) = decodeBoolean(get(descriptor, index)) + override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) = + decodeElement(descriptor, index, ::decodeBoolean) - override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) = decodeByte(get(descriptor, index)) + override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) = + decodeElement(descriptor, index, ::decodeByte) - override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = decodeChar(get(descriptor, index)) + override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = + decodeElement(descriptor, index, ::decodeChar) - override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = decodeDouble(get(descriptor, index)) + override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = + decodeElement(descriptor, index, ::decodeDouble) - override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = decodeFloat(get(descriptor, index)) + override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = + decodeElement(descriptor, index, ::decodeFloat) - override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) = decodeInt(get(descriptor, index)) + override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) = + decodeElement(descriptor, index, ::decodeInt) - override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) = decodeLong(get(descriptor, index)) + override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) = + decodeElement(descriptor, index, ::decodeLong) override fun decodeNullableSerializableElement( descriptor: SerialDescriptor, @@ -123,19 +142,37 @@ open class FirebaseCompositeDecoder( previousValue: T? ): T? { val isNullabilitySupported = deserializer.descriptor.isNullable - return if (isNullabilitySupported || decodeNotNullMark(get(descriptor, index))) decodeSerializableElement(descriptor, index, deserializer, previousValue) else decodeNull(get(descriptor, index)) + return if (isNullabilitySupported || decodeElement(descriptor, index, ::decodeNotNullMark)) { + decodeSerializableElement(descriptor, index, deserializer, previousValue) + } else { + decodeElement(descriptor, index, ::decodeNull) + } } - override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) = decodeShort(get(descriptor, index)) + override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) = + decodeElement(descriptor, index, ::decodeShort) - override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) = decodeString(get(descriptor, index)) + override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) = + decodeElement(descriptor, index, ::decodeString) override fun endStructure(descriptor: SerialDescriptor) {} @ExperimentalSerializationApi override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder = - FirebaseDecoder(get(descriptor, index)) - + decodeElement(descriptor, index) { + FirebaseDecoder(it, settings) + } + + private fun decodeElement(descriptor: SerialDescriptor, index: Int, decoder: (Any?) -> T): T { + return try { + decoder(get(descriptor, index)) + } catch (e: Exception) { + throw SerializationException( + message = "Exception during decoding ${descriptor.serialName} ${descriptor.getElementName(index)}", + cause = e + ) + } + } } private fun decodeString(value: Any?) = value.toString() @@ -201,5 +238,3 @@ internal fun SerialDescriptor.getElementIndexOrThrow(name: String): Int { private fun decodeNotNullMark(value: Any?) = value != null private fun decodeNull(value: Any?) = value as Nothing? - - diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 5e5c409b8..e10db675b 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -5,24 +5,50 @@ package dev.gitlive.firebase import kotlinx.serialization.* -import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* -import kotlinx.serialization.modules.EmptySerializersModule - -fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = - FirebaseEncoder(shouldEncodeElementDefault).apply { encodeSerializableValue(strategy, value) }.value//.also { println("encoded $it") } - -inline fun encode(value: T, shouldEncodeElementDefault: Boolean): Any? = value?.let { - FirebaseEncoder(shouldEncodeElementDefault).apply { encodeSerializableValue(it.firebaseSerializer(), it) }.value +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.SerializersModule + +fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = encode(strategy, value, EncodeSettings(shouldEncodeElementDefault)) + +fun encode(strategy: SerializationStrategy, value: T, settings: EncodeSettings): Any? = + FirebaseEncoder(settings).apply { encodeSerializableValue(strategy, value) }.value + +inline fun encode(value: T, shouldEncodeElementDefault: Boolean): Any? = encode(value, EncodeSettings(shouldEncodeElementDefault)) +inline fun encode(value: T, settings: EncodeSettings): Any? = value?.let { + FirebaseEncoder(settings).apply { + if (it is ValueWithSerializer<*> && it.value is T) { + @Suppress("UNCHECKED_CAST") + (it as ValueWithSerializer).let { + encodeSerializableValue(it.serializer, it.value) + } + } else { + encodeSerializableValue(it.firebaseSerializer(), it) + } + }.value } +/** + * An extension which which serializer to use for value. Handy in updating fields by name or path + * where using annotation is not possible + * @return a value with a custom serializer. + */ +fun T.withSerializer(serializer: SerializationStrategy): Any = ValueWithSerializer(this, serializer) +data class ValueWithSerializer(val value: T, val serializer: SerializationStrategy) + expect fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder -class FirebaseEncoder(internal val shouldEncodeElementDefault: Boolean) : Encoder { +class FirebaseEncoder( + internal val settings: EncodeSettings +) : Encoder { + +// constructor(shouldEncodeElementDefault: Boolean) : this(EncodeSettings(shouldEncodeElementDefault)) var value: Any? = null - override val serializersModule = EmptySerializersModule() + internal val shouldEncodeElementDefault = settings.shouldEncodeElementDefault + override val serializersModule: SerializersModule = settings.serializersModule + private var polymorphicDiscriminator: String? = null override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { @@ -83,7 +109,7 @@ class FirebaseEncoder(internal val shouldEncodeElementDefault: Boolean) : Encode } override fun encodeInline(descriptor: SerialDescriptor): Encoder = - FirebaseEncoder(shouldEncodeElementDefault) + FirebaseEncoder(settings) override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { encodePolymorphically(serializer, value) { @@ -93,23 +119,23 @@ class FirebaseEncoder(internal val shouldEncodeElementDefault: Boolean) : Encode } open class FirebaseCompositeEncoder constructor( - private val shouldEncodeElementDefault: Boolean, + private val settings: EncodeSettings, private val end: () -> Unit = {}, private val setPolymorphicType: (String, String) -> Unit = { _, _ -> }, private val set: (descriptor: SerialDescriptor, index: Int, value: Any?) -> Unit, ): CompositeEncoder { - override val serializersModule = EmptySerializersModule() - // private fun SerializationStrategy.toFirebase(): SerializationStrategy = when(descriptor.kind) { // StructureKind.MAP -> FirebaseMapSerializer(descriptor.getElementDescriptor(1)) as SerializationStrategy // StructureKind.LIST -> FirebaseListSerializer(descriptor.getElementDescriptor(0)) as SerializationStrategy // else -> this // } + override val serializersModule: SerializersModule = settings.serializersModule + override fun endStructure(descriptor: SerialDescriptor) = end() - override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int) = shouldEncodeElementDefault + override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int) = settings.shouldEncodeElementDefault override fun encodeNullableSerializableElement( descriptor: SerialDescriptor, @@ -120,7 +146,7 @@ open class FirebaseCompositeEncoder constructor( descriptor, index, value?.let { - FirebaseEncoder(shouldEncodeElementDefault).apply { + FirebaseEncoder(settings).apply { encodeSerializableValue(serializer, value) }.value } @@ -134,11 +160,13 @@ open class FirebaseCompositeEncoder constructor( ) = set( descriptor, index, - FirebaseEncoder(shouldEncodeElementDefault).apply { + FirebaseEncoder(settings).apply { encodeSerializableValue(serializer, value) }.value ) + fun encodeObject(descriptor: SerialDescriptor, index: Int, value: T) = set(descriptor, index, value) + override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) = set(descriptor, index, value) override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) = set(descriptor, index, value) @@ -159,10 +187,9 @@ open class FirebaseCompositeEncoder constructor( @ExperimentalSerializationApi override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder = - FirebaseEncoder(shouldEncodeElementDefault) + FirebaseEncoder(settings) fun encodePolymorphicClassDiscriminator(discriminator: String, type: String) { setPolymorphicType(discriminator, type) } } - diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt index e9ab9003f..68e9def69 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt @@ -4,10 +4,16 @@ package dev.gitlive.firebase -import kotlinx.serialization.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.serializer @Suppress("UNCHECKED_CAST") inline fun T.firebaseSerializer() = runCatching { serializer() } diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 24467783a..a26b99ea1 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -12,125 +12,169 @@ import kotlin.test.assertEquals import kotlin.test.assertNull import dev.gitlive.firebase.nativeAssertEquals import dev.gitlive.firebase.nativeMapOf +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.modules.SerializersModule @Serializable -data class TestData(val map: Map, val bool: Boolean = false, val nullableBool: Boolean? = null) +object TestObject { + val map = mapOf("key" to "value", "key2" to 12, "key3" to null) + val bool = false + val nullableBool: Boolean? = null +} + +@Serializable +data class TestData(val map: Map, val otherMap: Map, val bool: Boolean = false, val nullableBool: Boolean? = null) @Serializable -sealed class TestSealed { +sealed class SealedClass { @Serializable - @SerialName("child") - data class ChildClass(val map: Map, val bool: Boolean = false): TestSealed() + @SerialName("test") + data class Test(val value: String) : SealedClass() +} + +@Serializable +data class GenericClass( + val inner: T +) + +@Serializable +abstract class AbstractClass { + abstract val value: String } @Serializable -data class TestSealedList(val list: List) +@SerialName("implemented") +data class ImplementedClass(override val value: String, val otherValue: Boolean) : AbstractClass() + +@Serializable +data class NestedClass( + val sealed: SealedClass, + val abstract: AbstractClass, + val sealedList: List, + val abstractList: List, + val sealedMap: Map, + val abstractMap: Map +) class EncodersTest { + @Test - fun encodeMap() { - val encoded = encode(mapOf("key" to "value", "key2" to 12, "key3" to null), shouldEncodeElementDefault = true) + fun encodeDecodeList() { + val list = listOf("One", "Two", "Three") + val encoded = encode(list, shouldEncodeElementDefault = true) - nativeAssertEquals(nativeMapOf("key" to "value", "key2" to 12, "key3" to null), encoded) - } + nativeAssertEquals(nativeListOf("One", "Two", "Three"), encoded) - @Test - fun encodeObject() { - val encoded = encode(TestData.serializer(), TestData(mapOf("key" to "value"), true), shouldEncodeElementDefault = false) - nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "bool" to true), encoded) + val decoded = decode(ListSerializer(String.serializer()), encoded) + assertEquals(listOf("One", "Two", "Three"), decoded) } @Test - fun encodeObjectNullableValue() { - val encoded = encode(TestData.serializer(), TestData(mapOf("key" to "value"), true, nullableBool = true), shouldEncodeElementDefault = true) - nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "bool" to true, "nullableBool" to true), encoded) + fun encodeDecodeMap() { + val map = mapOf("key" to "value", "key2" to "value2", "key3" to "value3") + val encoded = encode(map, shouldEncodeElementDefault = true) + + nativeAssertEquals(nativeMapOf("key" to "value", "key2" to "value2", "key3" to "value3"), encoded) + + val decoded = decode(MapSerializer(String.serializer(), String.serializer()), encoded) + assertEquals(mapOf("key" to "value", "key2" to "value2", "key3" to "value3"), decoded) } @Test - fun encodeSealedClass() { - val encoded = encode(TestSealed.serializer(), TestSealed.ChildClass(mapOf("key" to "value"), true), shouldEncodeElementDefault = true) - nativeAssertEquals(nativeMapOf("type" to "child", "map" to nativeMapOf("key" to "value"), "bool" to true), encoded) + fun encodeDecodeObject() { + val encoded = encode(TestObject.serializer(), TestObject, shouldEncodeElementDefault = false) + nativeAssertEquals(nativeMapOf(), encoded) + + val decoded = decode(TestObject.serializer(), encoded) + assertEquals(TestObject, decoded) } @Test - fun decodeObject() { - val decoded = decode(TestData.serializer(), nativeMapOf("map" to nativeMapOf("key" to "value"))) - assertEquals(TestData(mapOf("key" to "value"), false), decoded) + fun encodeDecodeClass() { + val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) + val encoded = encode(TestData.serializer(), testDataClass, shouldEncodeElementDefault = false) + + nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true), encoded) + + val decoded = decode(TestData.serializer(), encoded) + assertEquals(testDataClass, decoded) } @Test - fun decodeListOfObjects() { - val decoded = decode(ListSerializer(TestData.serializer()), nativeListOf(nativeMapOf("map" to nativeMapOf("key" to "value")))) - assertEquals(listOf(TestData(mapOf("key" to "value"), false)), decoded) + fun encodeDecodeClassNullableValue() { + val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, nullableBool = true) + val encoded = encode(TestData.serializer(), testDataClass, shouldEncodeElementDefault = true) + + nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), encoded) + + val decoded = decode(TestData.serializer(), encoded) + assertEquals(testDataClass, decoded) } @Test - fun decodeObjectNullableValue() { - val decoded = decode(TestData.serializer(), nativeMapOf("map" to mapOf("key" to "value"), "nullableBool" to null)) - assertNull(decoded.nullableBool) + fun encodeDecodeGenericClass() { + val innerClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) + val genericClass = GenericClass(innerClass) + val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass, shouldEncodeElementDefault = true) + + nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null)), encoded) + + val decoded = decode(GenericClass.serializer(TestData.serializer()), encoded) + assertEquals(genericClass, decoded) } @Test - fun decodeSealedClass() { - val decoded = decode(TestSealed.serializer(), nativeMapOf("type" to "child", "map" to nativeMapOf("key" to "value"), "bool" to true)) - assertEquals(TestSealed.ChildClass(mapOf("key" to "value"), true), decoded) + fun encodeDecodeSealedClass() { + val sealedClass = SealedClass.Test("value") + val encoded = encode(SealedClass.serializer(), sealedClass, shouldEncodeElementDefault = true) + + nativeAssertEquals(nativeMapOf("type" to "test", "value" to "value"), encoded) + + val decoded = decode(SealedClass.serializer(), encoded) + assertEquals(sealedClass, decoded) } @Test - fun encodeSealedClassList() { - val toEncode = TestSealedList( - list = listOf( - TestSealed.ChildClass( - map = mapOf("key" to "value"), - bool = false - ) - ) - ) - val encoded = encode( - TestSealedList.serializer(), - toEncode, - shouldEncodeElementDefault = true - ) - val expected = nativeMapOf( - "list" to nativeListOf( - nativeMapOf( - "type" to "child", - "map" to nativeMapOf( - "key" to "value" - ), - "bool" to false - ) - ) - ) - nativeAssertEquals(expected, encoded) + fun encodeDecodePolymorphicClass() { + val module = SerializersModule { + polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) + } + val abstractClass: AbstractClass = ImplementedClass("value", true) + val encoded = encode(AbstractClass.serializer(), abstractClass, EncodeSettings(true, module)) + + nativeAssertEquals(nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true), encoded) + + val decoded = decode(AbstractClass.serializer(), encoded, DecodeSettings(module)) + assertEquals(abstractClass, decoded) } @Test - fun decodeSealedClassList() { - val toDecode = nativeMapOf( - "list" to nativeListOf( - nativeMapOf( - "type" to "child", - "map" to nativeMapOf( - "key" to "value" - ), - "bool" to false - ) - ) - ) - val decoded = decode( - TestSealedList.serializer(), - toDecode - ) - val expected = TestSealedList( - list = listOf( - TestSealed.ChildClass( - map = mapOf("key" to "value"), - bool = false - ) - ) + fun encodeDecodeNestedClass() { + val module = SerializersModule { + polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) + } + + val sealedClass: SealedClass = SealedClass.Test("value") + val abstractClass: AbstractClass = ImplementedClass("value", true) + val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) + val encoded = encode(NestedClass.serializer(), nestedClass, EncodeSettings(true, module)) + + val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") + val abstractEncoded = nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true) + nativeAssertEquals( + nativeMapOf( + "sealed" to sealedEncoded, + "abstract" to abstractEncoded, + "sealedList" to nativeListOf(sealedEncoded), + "abstractList" to nativeListOf(abstractEncoded), + "sealedMap" to nativeMapOf(sealedEncoded to sealedEncoded), + "abstractMap" to nativeMapOf(abstractEncoded to abstractEncoded) + ), + encoded ) - assertEquals(expected, decoded) + val decoded = decode(NestedClass.serializer(), encoded, DecodeSettings(module)) + assertEquals(nestedClass, decoded) } -} \ No newline at end of file +} diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt index ddc5843c1..6576c3ada 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt @@ -9,32 +9,32 @@ import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind -actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder = when(descriptor.kind) { - StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> (value as Map<*, *>).let { map -> - FirebaseClassDecoder(map.size, { map.containsKey(it) }) { desc, index -> - val elementName = desc.getElementName(index) - if (desc.kind is PolymorphicKind && elementName == "value") { - map - } else { - map[desc.getElementName(index)] - } - } - } - StructureKind.LIST -> - when(value) { - is List<*> -> value - is Map<*, *> -> value.asSequence() - .sortedBy { (it) -> it.toString().toIntOrNull() } - .map { (_, it) -> it } - .toList() - else -> error("unexpected type, got $value when expecting a list") - } - .let { FirebaseCompositeDecoder(it.size) { _, index -> it[index] } } - StructureKind.MAP -> (value as Map<*, *>).entries.toList().let { - FirebaseCompositeDecoder(it.size) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } } +actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when(descriptor.kind) { + StructureKind.CLASS, StructureKind.OBJECT -> decodeAsMap(false) + StructureKind.LIST -> decodeAsList() + StructureKind.MAP -> (value as? Map<*, *>).orEmpty().entries.toList().let { + FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } } } + is PolymorphicKind -> decodeAsMap(polymorphicIsNested) else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } actual fun getPolymorphicType(value: Any?, discriminator: String): String = - (value as Map<*,*>)[discriminator] as String \ No newline at end of file + (value as? Map<*,*>).orEmpty()[discriminator] as String + +private fun FirebaseDecoder.decodeAsList(): CompositeDecoder = (value as? List<*>).orEmpty().let { + FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index] } +} +private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map -> + FirebaseClassDecoder(map.size, settings, { map.containsKey(it) }) { desc, index -> + if (isNestedPolymorphic) { + if (index == 0) + map[desc.getElementName(index)] + else { + map + } + } else { + map[desc.getElementName(index)] + } + } +} diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt index d4584218a..27dcb5820 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -11,18 +11,25 @@ import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { - StructureKind.LIST -> mutableListOf() - .also { value = it } - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault) { _, index, value -> it.add(index, value) } } + StructureKind.LIST -> encodeAsList() StructureKind.MAP -> mutableListOf() - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } } - StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> mutableMapOf() - .also { value = it } - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault, + .let { FirebaseCompositeEncoder(settings, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } } + StructureKind.CLASS, StructureKind.OBJECT-> encodeAsMap(descriptor) + is PolymorphicKind -> encodeAsMap(descriptor) + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") +} + +private fun FirebaseEncoder.encodeAsList(): FirebaseCompositeEncoder = mutableListOf() + .also { value = it } + .let { FirebaseCompositeEncoder(settings) { _, index, value -> it.add(index, value) } } +private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = mutableMapOf() + .also { value = it } + .let { + FirebaseCompositeEncoder( + settings, setPolymorphicType = { discriminator, type -> it[discriminator] = type }, set = { _, index, value -> it[descriptor.getElementName(index)] = value } - ) } - else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") -} \ No newline at end of file + ) + } diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt index b64f97129..37dc76017 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt @@ -5,39 +5,51 @@ package dev.gitlive.firebase import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.encoding.CompositeDecoder import kotlin.js.Json -@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") -actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor): CompositeDecoder = when(descriptor.kind) { - StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> (value as Json).let { json -> - FirebaseClassDecoder(js("Object").keys(value).length as Int, { json[it] != undefined }) { desc, index -> - val elementName = desc.getElementName(index) - if (desc.kind is PolymorphicKind && elementName == "value") { - json +actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when (descriptor.kind) { + StructureKind.CLASS, StructureKind.OBJECT -> decodeAsMap(false) + StructureKind.LIST -> decodeAsList() + StructureKind.MAP -> (js("Object").entries(value) as Array>).let { + FirebaseCompositeDecoder( + it.size, + settings + ) { desc, index -> it[index / 2].run { if (index % 2 == 0) { + val key = get(0) as String + if (desc.getElementDescriptor(index).kind == PrimitiveKind.STRING) { + key } else { - json[desc.getElementName(index)] + JSON.parse(key) } - } - } - StructureKind.LIST -> - when(value) { - is Array<*> -> value.toList() - else -> (js("Object").entries(value) as Array>) - .asSequence() - .sortedBy { (it) -> it.toString().toIntOrNull() } - .map { (_, it) -> it } - .toList() - } - .let { FirebaseCompositeDecoder(it.size) { _, index -> it[index] } } - StructureKind.MAP -> (js("Object").entries(value) as Array>).let { - FirebaseCompositeDecoder(it.size) { _, index -> it[index/2].run { if(index % 2 == 0) get(0) else get(1) } } + } else get(1) } } } + + is PolymorphicKind -> decodeAsMap(polymorphicIsNested) else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") actual fun getPolymorphicType(value: Any?, discriminator: String): String = (value as Json)[discriminator] as String + +private fun FirebaseDecoder.decodeAsList(): CompositeDecoder = (value as Array<*>).let { + FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index] } +} +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") +private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as Json).let { json -> + FirebaseClassDecoder(js("Object").keys(value).length as Int, settings, { json[it] != undefined }) { desc, index -> + if (isNestedPolymorphic) { + if (index == 0) { + json[desc.getElementName(index)] + } else { + json + } + } else { + json[desc.getElementName(index)] + } + } +} \ No newline at end of file diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index 69959f4ed..299d52c68 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -11,24 +11,35 @@ import kotlinx.serialization.descriptors.StructureKind import kotlin.js.json actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { - StructureKind.LIST -> Array(descriptor.elementsCount) { null } - .also { value = it } - .let { FirebaseCompositeEncoder(shouldEncodeElementDefault) { _, index, value -> it[index] = value } } + StructureKind.LIST -> encodeAsList(descriptor) StructureKind.MAP -> { val map = json() - var lastKey: String = "" + var lastKey = "" value = map - FirebaseCompositeEncoder(shouldEncodeElementDefault) { _, index, value -> if(index % 2 == 0) lastKey = value as String else map[lastKey] = value } + FirebaseCompositeEncoder(settings) { _, index, value -> + if (index % 2 == 0) { + lastKey = (value as? String) ?: JSON.stringify(value) + } else { + map[lastKey] = value + } + } } - StructureKind.CLASS, StructureKind.OBJECT, PolymorphicKind.SEALED -> json() - .also { value = it } - .let { FirebaseCompositeEncoder( - shouldEncodeElementDefault, + StructureKind.CLASS, StructureKind.OBJECT -> encodeAsMap(descriptor) + is PolymorphicKind -> encodeAsMap(descriptor) + else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") +} + +private fun FirebaseEncoder.encodeAsList(descriptor: SerialDescriptor): FirebaseCompositeEncoder = Array(descriptor.elementsCount) { null } + .also { value = it } + .let { FirebaseCompositeEncoder(settings) { _, index, value -> it[index] = value } } +private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = json() + .also { value = it } + .let { + FirebaseCompositeEncoder( + settings, setPolymorphicType = { discriminator, type -> it[discriminator] = type }, set = { _, index, value -> it[descriptor.getElementName(index)] = value } - ) } - else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") -} - + ) + } 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 0ce0e0d12..ce075b2e6 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 @@ -6,12 +6,9 @@ package dev.gitlive.firebase.database import com.google.android.gms.tasks.Task import com.google.firebase.database.* -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.* import dev.gitlive.firebase.database.ChildEvent.Type import dev.gitlive.firebase.database.FirebaseDatabase.Companion.FirebaseDatabase -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking @@ -23,18 +20,17 @@ import kotlinx.serialization.SerializationStrategy import java.util.* import kotlin.time.Duration.Companion.seconds -suspend fun Task.awaitWhileOnline(): T = +suspend fun Task.awaitWhileOnline(database: FirebaseDatabase): T = merge( flow { emit(await()) }, - Firebase.database + database .reference(".info/connected") .valueEvents .debounce(2.seconds) .filterNot { it.value() } .map { throw DatabaseException("Database not connected", null) } ) - .first() - + .first() actual val Firebase.database by lazy { FirebaseDatabase(com.google.firebase.database.FirebaseDatabase.getInstance()) } @@ -163,38 +159,39 @@ actual class DatabaseReference internal constructor( ): Query(android, persistenceEnabled) { actual val key get() = android.key + val database = FirebaseDatabase(android.database) actual fun child(path: String) = DatabaseReference(android.child(path), persistenceEnabled) actual fun push() = DatabaseReference(android.push(), persistenceEnabled) - actual fun onDisconnect() = OnDisconnect(android.onDisconnect(), persistenceEnabled) + actual fun onDisconnect() = OnDisconnect(android.onDisconnect(), persistenceEnabled, database) - actual suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = android.setValue(encode(value, encodeDefaults)) - .run { if(persistenceEnabled) await() else awaitWhileOnline() } + actual suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings) = android.setValue(encode(value, encodeSettings)) + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = - android.setValue(encode(strategy, value, encodeDefaults)) - .run { if(persistenceEnabled) await() else awaitWhileOnline() } + actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = + android.setValue(encode(strategy, value, encodeSettings)) + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @Suppress("UNCHECKED_CAST") - actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = - android.updateChildren(encode(update, encodeDefaults) as Map) - .run { if(persistenceEnabled) await() else awaitWhileOnline() } + actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = + android.updateChildren(encode(update, encodeSettings) as Map) + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } actual suspend fun removeValue() = android.removeValue() - .run { if(persistenceEnabled) await() else awaitWhileOnline() } + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun runTransaction(strategy: KSerializer, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() android.runTransaction(object : Transaction.Handler { override fun doTransaction(currentData: MutableData): Transaction.Result { currentData.value = currentData.value?.let { - transactionUpdate(decode(strategy, it)) + transactionUpdate(decode(strategy, it, decodeSettings)) } return Transaction.success(currentData) } @@ -232,8 +229,8 @@ actual class DataSnapshot internal constructor( actual inline fun value() = decode(value = android.value) - actual fun value(strategy: DeserializationStrategy) = - decode(strategy, android.value) + actual fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = + decode(strategy, android.value, decodeSettings) actual fun child(path: String) = DataSnapshot(android.child(path), persistenceEnabled) actual val hasChildren get() = android.hasChildren() @@ -242,30 +239,31 @@ actual class DataSnapshot internal constructor( actual class OnDisconnect internal constructor( val android: com.google.firebase.database.OnDisconnect, - val persistenceEnabled: Boolean + val persistenceEnabled: Boolean, + val database: FirebaseDatabase, ) { actual suspend fun removeValue() = android.removeValue() - .run { if(persistenceEnabled) await() else awaitWhileOnline() } + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } actual suspend fun cancel() = android.cancel() - .run { if(persistenceEnabled) await() else awaitWhileOnline() } + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend inline fun setValue(value: T, encodeDefaults: Boolean) = - android.setValue(encode(value, encodeDefaults)) - .run { if(persistenceEnabled) await() else awaitWhileOnline() } + actual suspend inline fun setValue(value: T, encodeSettings: EncodeSettings) = + android.setValue(encode(value, encodeSettings)) + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = - android.setValue(encode(strategy, value, encodeDefaults)) - .run { if(persistenceEnabled) await() else awaitWhileOnline() } + actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = + android.setValue(encode(strategy, value, encodeSettings)) + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit} - actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = - android.updateChildren(update.mapValues { (_, it) -> encode(it, encodeDefaults) }) - .run { if(persistenceEnabled) await() else awaitWhileOnline() } + actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = + android.updateChildren(update.mapValues { (_, it) -> encode(it, encodeSettings) }) + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } } diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index d5bbf6aee..e2fdf22a8 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 @@ -4,6 +4,8 @@ package dev.gitlive.firebase.database +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type.* @@ -69,12 +71,12 @@ expect class DatabaseReference : Query { fun push(): DatabaseReference fun child(path: String): DatabaseReference fun onDisconnect(): OnDisconnect - suspend inline fun setValue(value: T?, encodeDefaults: Boolean = true) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean = true) - suspend fun updateChildren(update: Map, encodeDefaults: Boolean = true) + suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings = EncodeSettings()) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) + suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) suspend fun removeValue() - suspend fun runTransaction(strategy: KSerializer, transactionUpdate: (currentData: T) -> T): DataSnapshot + suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings = DecodeSettings(), transactionUpdate: (currentData: T) -> T): DataSnapshot } expect class DataSnapshot { @@ -83,7 +85,7 @@ expect class DataSnapshot { val ref: DatabaseReference val value: Any? inline fun value(): T - fun value(strategy: DeserializationStrategy): T + fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings()): T fun child(path: String): DataSnapshot val hasChildren: Boolean val children: Iterable @@ -94,7 +96,7 @@ expect class DatabaseException(message: String?, cause: Throwable?) : RuntimeExc expect class OnDisconnect { suspend fun removeValue() suspend fun cancel() - suspend inline fun setValue(value: T, encodeDefaults: Boolean = true) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean = true) - suspend fun updateChildren(update: Map, encodeDefaults: Boolean = true) + suspend inline fun setValue(value: T, encodeSettings: EncodeSettings = EncodeSettings()) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) + suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) } 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 128d7b6c2..e1f0198f7 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 @@ -6,12 +6,9 @@ package dev.gitlive.firebase.database import cocoapods.FirebaseDatabase.* import cocoapods.FirebaseDatabase.FIRDataEventType.* -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.* import dev.gitlive.firebase.database.ChildEvent.Type import dev.gitlive.firebase.database.ChildEvent.Type.* -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose @@ -139,29 +136,29 @@ actual class DatabaseReference internal constructor( actual fun push() = DatabaseReference(ios.childByAutoId(), persistenceEnabled) actual fun onDisconnect() = OnDisconnect(ios, persistenceEnabled) - actual suspend inline fun setValue(value: T?, encodeDefaults: Boolean) { - ios.await(persistenceEnabled) { setValue(encode(value, encodeDefaults), it) } + actual suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings) { + ios.await(persistenceEnabled) { setValue(encode(value, encodeSettings), it) } } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) { - ios.await(persistenceEnabled) { setValue(encode(strategy, value, encodeDefaults), it) } + actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) { + ios.await(persistenceEnabled) { setValue(encode(strategy, value, encodeSettings), it) } } @Suppress("UNCHECKED_CAST") - actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) { - ios.await(persistenceEnabled) { updateChildValues(encode(update, encodeDefaults) as Map, it) } + actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { + ios.await(persistenceEnabled) { updateChildValues(encode(update, encodeSettings) as Map, it) } } actual suspend fun removeValue() { ios.await(persistenceEnabled) { removeValueWithCompletionBlock(it) } } - actual suspend fun runTransaction(strategy: KSerializer, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() ios.runTransactionBlock( block = { firMutableData -> firMutableData?.value = firMutableData?.value?.let { - transactionUpdate(decode(strategy, it)) + transactionUpdate(decode(strategy, it, decodeSettings)) } FIRTransactionResult.successWithValue(firMutableData!!) }, @@ -195,8 +192,8 @@ actual class DataSnapshot internal constructor( actual inline fun value() = decode(value = ios.value) - actual fun value(strategy: DeserializationStrategy) = - decode(strategy, ios.value) + actual fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = + decode(strategy, ios.value, decodeSettings) actual fun child(path: String) = DataSnapshot(ios.childSnapshotForPath(path), persistenceEnabled) actual val hasChildren get() = ios.hasChildren() @@ -215,17 +212,17 @@ actual class OnDisconnect internal constructor( ios.await(persistenceEnabled) { cancelDisconnectOperationsWithCompletionBlock(it) } } - actual suspend inline fun setValue(value: T, encodeDefaults: Boolean) { - ios.await(persistenceEnabled) { onDisconnectSetValue(encode(value, encodeDefaults), it) } + actual suspend inline fun setValue(value: T, encodeSettings: EncodeSettings) { + ios.await(persistenceEnabled) { onDisconnectSetValue(encode(value, encodeSettings), it) } } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) { - ios.await(persistenceEnabled) { onDisconnectSetValue(encode(strategy, value, encodeDefaults), it) } + actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) { + ios.await(persistenceEnabled) { onDisconnectSetValue(encode(strategy, value, encodeSettings), it) } } @Suppress("UNCHECKED_CAST") - actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) { - ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it, encodeDefaults) } as Map, it) } + actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { + ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it, encodeSettings) } as Map, it) } } } diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index d4d47b6be..9d12693e5 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 @@ -29,6 +29,7 @@ import dev.gitlive.firebase.database.externals.limitToLast as jsLimitToLast import dev.gitlive.firebase.database.externals.orderByChild as jsOrderByChild import dev.gitlive.firebase.database.externals.orderByKey as jsOrderByKey import dev.gitlive.firebase.database.externals.orderByValue as jsOrderByValue +import dev.gitlive.firebase.database.externals.runTransaction as jsRunTransaction import dev.gitlive.firebase.database.externals.startAt as jsStartAt actual val Firebase.database @@ -137,27 +138,21 @@ actual class DatabaseReference internal constructor( actual fun onDisconnect() = rethrow { OnDisconnect(onDisconnect(js), database) } - actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = - rethrow { update(js, encode(update, encodeDefaults) ?: json()).awaitWhileOnline(database) } + actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = + rethrow { update(js, encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } actual suspend fun removeValue() = rethrow { remove(js).awaitWhileOnline(database) } - actual suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = rethrow { - set(js, encode(value, encodeDefaults)).awaitWhileOnline(database) + actual suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings) = rethrow { + set(js, encode(value, encodeSettings)).awaitWhileOnline(database) } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = - rethrow { set(js, encode(strategy, value, encodeDefaults)).awaitWhileOnline(database) } + actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = + rethrow { set(js, encode(strategy, value, encodeSettings)).awaitWhileOnline(database) } - actual suspend fun runTransaction(strategy: KSerializer, transactionUpdate: (currentData: T) -> T): DataSnapshot = - rethrow { - val result = runTransaction( - js, - transactionUpdate, - ).awaitWhileOnline(database) - - DataSnapshot(result.snapshot, database) - } + actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { + return DataSnapshot(jsRunTransaction(js, transactionUpdate).awaitWhileOnline(database).snapshot, database) + } } actual class DataSnapshot internal constructor( @@ -172,8 +167,8 @@ actual class DataSnapshot internal constructor( actual inline fun value() = rethrow { decode(value = js.`val`()) } - actual fun value(strategy: DeserializationStrategy) = - rethrow { decode(strategy, js.`val`()) } + actual fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = + rethrow { decode(strategy, js.`val`(), decodeSettings) } actual val exists get() = rethrow { js.exists() } actual val key get() = rethrow { js.key } @@ -197,14 +192,14 @@ actual class OnDisconnect internal constructor( actual suspend fun removeValue() = rethrow { js.remove().awaitWhileOnline(database) } actual suspend fun cancel() = rethrow { js.cancel().awaitWhileOnline(database) } - actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = - rethrow { js.update(encode(update, encodeDefaults) ?: json()).awaitWhileOnline(database) } + actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = + rethrow { js.update(encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } - actual suspend inline fun setValue(value: T, encodeDefaults: Boolean) = - rethrow { js.set(encode(value, encodeDefaults)).awaitWhileOnline(database) } + actual suspend inline fun setValue(value: T, encodeSettings: EncodeSettings) = + rethrow { js.set(encode(value, encodeSettings)).awaitWhileOnline(database) } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = - rethrow { js.set(encode(strategy, value, encodeDefaults)).awaitWhileOnline(database) } + actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = + rethrow { js.set(encode(strategy, value, encodeSettings)).awaitWhileOnline(database) } } actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) { diff --git a/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt new file mode 100644 index 000000000..1f70d3731 --- /dev/null +++ b/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -0,0 +1,6 @@ +package dev.gitlive.firebase.firestore + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreJs +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index cf185f630..8c6035f28 100644 --- a/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -11,5 +11,5 @@ 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 fun encodedAsMap(encoded: Any?): Map = encoded as Map +actual fun Map.asEncoded(): Any = this diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt new file mode 100644 index 000000000..f5f2cee34 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt @@ -0,0 +1,26 @@ +package dev.gitlive.firebase.firestore + +import kotlinx.serialization.Serializable + +/** Represents a platform specific Firebase FieldValue. */ +typealias NativeFieldValue = com.google.firebase.firestore.FieldValue + +/** Represents a Firebase FieldValue. */ +@Serializable(with = FieldValueSerializer::class) +actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { + init { + require(nativeValue is NativeFieldValue) + } + override fun equals(other: Any?): Boolean = + this === other || other is FieldValue && nativeValue == other.nativeValue + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() + + actual companion object { + actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.serverTimestamp()) + actual val delete: FieldValue get() = FieldValue(NativeFieldValue.delete()) + actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.increment(value.toDouble())) + actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayUnion(*elements)) + actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayRemove(*elements)) + } +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt new file mode 100644 index 000000000..d12bda859 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -0,0 +1,10 @@ +package dev.gitlive.firebase.firestore + +@PublishedApi +internal actual fun isSpecialValue(value: Any) = when(value) { + is NativeFieldValue, + is NativeGeoPoint, + is NativeTimestamp, + is NativeDocumentReference -> true + else -> false +} 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 dcc6a2ebc..aa76abbd3 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 @@ -8,10 +8,13 @@ package dev.gitlive.firebase.firestore import com.google.android.gms.tasks.Task import com.google.firebase.firestore.* import dev.gitlive.firebase.* +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.tasks.asDeferred import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.Serializable @@ -23,19 +26,14 @@ actual val Firebase.firestore get() = actual fun Firebase.firestore(app: FirebaseApp) = FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance(app.android)) -/** Helper method to perform an update operation. */ -@JvmName("performUpdateFields") -private fun performUpdate( - fieldsAndValues: Array>, - update: (String, Any?, Array) -> R -) = performUpdate(fieldsAndValues, { it }, { encode(it, true) }, update) - -/** Helper method to perform an update operation. */ -@JvmName("performUpdateFieldPaths") -private fun performUpdate( - fieldsAndValues: Array>, - update: (com.google.firebase.firestore.FieldPath, Any?, Array) -> R -) = performUpdate(fieldsAndValues, { it.android }, { encode(it, true) }, update) +@Suppress("DeferredIsResult") +@PublishedApi +internal fun Task.asUnitDeferred(): Deferred = CompletableDeferred() + .apply { + asDeferred().invokeOnCompletion { exception -> + if (exception == null) complete(Unit) else completeExceptionally(exception) + } + } actual class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) { @@ -50,7 +48,7 @@ actual class FirebaseFirestore(val android: com.google.firebase.firestore.Fireba actual fun setLoggingEnabled(loggingEnabled: Boolean) = com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) - actual suspend fun runTransaction(func: suspend Transaction.() -> T) = + actual suspend fun runTransaction(func: suspend Transaction.() -> T): T = android.runTransaction { runBlocking { Transaction(it).func() } }.await() actual suspend fun clearPersistence() = @@ -80,114 +78,116 @@ actual class FirebaseFirestore(val android: com.google.firebase.firestore.Fireba } -actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) { - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(documentRef.android, encode(data, encodeDefaults)!!) - }.let { this } - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .let { this } +val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { + is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() + is SetOptions.Overwrite -> null + is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) + is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) +} - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .let { this } +actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) : BaseWriteBatch() { - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!) - }.let { this } + actual val async = Async(android) - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .let { this } + override fun setEncoded( + documentRef: DocumentReference, + encodedData: Any, + setOptions: SetOptions + ): BaseWriteBatch = (setOptions.android?.let { + android.set(documentRef.android, encodedData, it) + } ?: android.set(documentRef.android, encodedData)).let { + this + } - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .let { this } + @Suppress("UNCHECKED_CAST") + override fun setEncoded( + documentRef: DocumentReference, + encodedData: Any, + encodedFieldsAndValues: List>, + merge: Boolean + ): BaseWriteBatch { + val serializedItem = encodedData as Map + val serializedFieldAndValues = encodedFieldsAndValues.toMap() + + val result = serializedItem + serializedFieldAndValues + if (merge) { + android.set(documentRef.android, result, com.google.firebase.firestore.SetOptions.merge()) + } else { + android.set(documentRef.android, result) + } + return this + } @Suppress("UNCHECKED_CAST") - actual inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = - android.update(documentRef.android, encode(data, encodeDefaults) as Map).let { this } + override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = android.update(documentRef.android, encodedData as Map).let { this } @Suppress("UNCHECKED_CAST") - actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - android.update(documentRef.android, encode(strategy, data, encodeDefaults) as Map).let { this } + override fun updateEncoded( + documentRef: DocumentReference, + encodedData: Any, + encodedFieldsAndValues: List> + ): BaseWriteBatch { + val serializedItem = encodedData as Map + val serializedFieldAndValues = encodedFieldsAndValues.toMap() + + val result = serializedItem + serializedFieldAndValues + return android.update(documentRef.android, result).let { this } + } - @JvmName("updateFields") - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } + override fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseWriteBatch = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + android.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } - @JvmName("updateFieldPaths") - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } + override fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseWriteBatch = 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().run { Unit } + actual suspend fun commit() = async.commit().await() + @Suppress("DeferredIsResult") + actual class Async(private val android: com.google.firebase.firestore.WriteBatch) { + actual fun commit(): Deferred = android.commit().asUnitDeferred() + } } -actual class Transaction(val android: com.google.firebase.firestore.Transaction) { - - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(documentRef.android, encode(data, encodeDefaults)!!) - }.let { this } - - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .let { this } +actual class Transaction(val android: com.google.firebase.firestore.Transaction) : BaseTransaction() { - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .let { this } - - actual fun set( + override fun setEncoded( documentRef: DocumentReference, - strategy: SerializationStrategy, - data: T, - encodeDefaults: Boolean, - merge: Boolean - ) = when(merge) { - true -> android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!) - }.let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .let { this } - - @Suppress("UNCHECKED_CAST") - actual fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = - android.update(documentRef.android, encode(data, encodeDefaults) as Map).let { this } + encodedData: Any, + setOptions: SetOptions + ): BaseTransaction { + setOptions.android?.let { + android.set(documentRef.android, encodedData, it) + } ?: android.set(documentRef.android, encodedData) + return this + } @Suppress("UNCHECKED_CAST") - actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - android.update(documentRef.android, encode(strategy, data, encodeDefaults) as Map).let { this } + override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = android.update(documentRef.android, encodedData as Map).let { this } - @JvmName("updateFields") - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } + override fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseTransaction = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + android.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } - @JvmName("updateFieldPaths") - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } + override 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 } @@ -200,7 +200,7 @@ actual class Transaction(val android: com.google.firebase.firestore.Transaction) actual typealias NativeDocumentReference = com.google.firebase.firestore.DocumentReference @Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) { +actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { val android: NativeDocumentReference by ::nativeValue actual val id: String get() = android.id @@ -211,58 +211,9 @@ actual class DocumentReference actual constructor(internal actual val nativeValu actual val parent: CollectionReference get() = CollectionReference(android.parent) - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) - - actual suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(encode(data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(encode(data, encodeDefaults)!!) - }.await().run { Unit } - - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(encode(data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .await().run { Unit } - - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(encode(data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .await().run { Unit } - - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(encode(strategy, data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(encode(strategy, data, encodeDefaults)!!) - }.await().run { Unit } - - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .await().run { Unit } + override val async = Async(android) - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .await().run { Unit } - - @Suppress("UNCHECKED_CAST") - actual suspend inline fun update(data: T, encodeDefaults: Boolean) = - android.update(encode(data, encodeDefaults) as Map).await().run { Unit } - - @Suppress("UNCHECKED_CAST") - actual suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - android.update(encode(strategy, data, encodeDefaults) as Map).await().run { Unit } - - @JvmName("updateFields") - actual suspend fun update(vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(field, value, *moreFieldsAndValues) - }?.await() - .run { Unit } - - @JvmName("updateFieldPaths") - actual suspend fun update(vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(field, value, *moreFieldsAndValues) - }?.await() - .run { Unit } - - actual suspend fun delete() = - android.delete().await().run { Unit } + actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) actual suspend fun get() = DocumentSnapshot(android.get().await()) @@ -277,10 +228,34 @@ actual class DocumentReference actual constructor(internal actual val nativeValu } awaitClose { listener.remove() } } + override fun equals(other: Any?): Boolean = this === other || other is DocumentReference && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() + + @Suppress("DeferredIsResult") + class Async(@PublishedApi internal val android: NativeDocumentReference) : BaseDocumentReference.Async() { + + override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = (setOptions.android?.let { + android.set(encodedData, it) + } ?: android.set(encodedData)).asUnitDeferred() + + @Suppress("UNCHECKED_CAST") + override fun updateEncoded(encodedData: Any): Deferred = android.update(encodedData as Map).asUnitDeferred() + + override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { + android.update(encodedFieldsAndValues.toMap()) + }?.asUnitDeferred() ?: CompletableDeferred(Unit) + + override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + android.update(field, value, *moreFieldsAndValues) + }?.asUnitDeferred() ?: CompletableDeferred(Unit) + + override fun delete() = + android.delete().asUnitDeferred() + } } actual open class Query(open val android: com.google.firebase.firestore.Query) { @@ -361,6 +336,7 @@ actual class CollectionReference(override val android: com.google.firebase.fires actual val path: String get() = android.path + actual val async = Async(android) actual val document: DocumentReference get() = DocumentReference(android.document()) @@ -370,13 +346,18 @@ actual class CollectionReference(override val android: com.google.firebase.fires actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) - actual suspend inline fun add(data: T, encodeDefaults: Boolean) = - DocumentReference(android.add(encode(data, encodeDefaults)!!).await()) - - actual suspend fun add(data: T, strategy: SerializationStrategy, encodeDefaults: Boolean) = - DocumentReference(android.add(encode(strategy, data, encodeDefaults)!!).await()) - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - DocumentReference(android.add(encode(strategy, data, encodeDefaults)!!).await()) + actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = + DocumentReference(android.add(encode(data, encodeSettings)!!).await()) + actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = + DocumentReference(android.add(encode(strategy, data, encodeSettings)!!).await()) + + @Suppress("DeferredIsResult") + actual class Async(@PublishedApi internal val android: com.google.firebase.firestore.CollectionReference) { + actual inline fun add(data: T, encodeSettings: EncodeSettings) = + android.add(encode(data, encodeSettings)!!).asDeferred().convert(::DocumentReference) + actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = + android.add(encode(strategy, data, encodeSettings)!!).asDeferred().convert(::DocumentReference) + } } actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException @@ -413,14 +394,14 @@ actual class DocumentSnapshot(val android: com.google.firebase.firestore.Documen actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T = decode(value = android.getData(serverTimestampBehavior.toAndroid())) - actual fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.getData(serverTimestampBehavior.toAndroid())) + actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = + decode(strategy, android.getData(serverTimestampBehavior.toAndroid()), decodeSettings) actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior): T = decode(value = android.get(field, serverTimestampBehavior.toAndroid())) - actual fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.get(field, serverTimestampBehavior.toAndroid())) + actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = + decode(strategy, android.get(field, serverTimestampBehavior.toAndroid()), decodeSettings) actual fun contains(field: String) = android.contains(field) @@ -437,37 +418,21 @@ actual class DocumentSnapshot(val android: com.google.firebase.firestore.Documen actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { actual val hasPendingWrites: Boolean get() = android.hasPendingWrites() - actual val isFromCache: Boolean get() = android.isFromCache() + actual val isFromCache: Boolean get() = android.isFromCache } actual class FieldPath private constructor(val android: com.google.firebase.firestore.FieldPath) { - actual constructor(vararg fieldNames: String) : this(com.google.firebase.firestore.FieldPath.of(*fieldNames)) - actual val documentId: FieldPath get() = FieldPath(com.google.firebase.firestore.FieldPath.documentId()) + actual constructor(vararg fieldNames: String) : this( + com.google.firebase.firestore.FieldPath.of( + *fieldNames + ) + ) + actual val documentId: FieldPath get() = FieldPath(com.google.firebase.firestore.FieldPath.documentId()) + actual val encoded: EncodedFieldPath = android override fun equals(other: Any?): Boolean = other is FieldPath && android == other.android override fun hashCode(): Int = android.hashCode() override fun toString(): String = android.toString() } -/** Represents a platform specific Firebase FieldValue. */ -private typealias NativeFieldValue = com.google.firebase.firestore.FieldValue - -/** Represents a Firebase FieldValue. */ -@Serializable(with = FieldValueSerializer::class) -actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { - init { - require(nativeValue is NativeFieldValue) - } - override fun equals(other: Any?): Boolean = - this === other || other is FieldValue && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - actual companion object { - actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.serverTimestamp()) - actual val delete: FieldValue get() = FieldValue(NativeFieldValue.delete()) - actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.increment(value.toDouble())) - actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayUnion(*elements)) - actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayRemove(*elements)) - } -} +actual typealias EncodedFieldPath = com.google.firebase.firestore.FieldPath diff --git a/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt new file mode 100644 index 000000000..6deceefb3 --- /dev/null +++ b/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -0,0 +1,7 @@ +package dev.gitlive.firebase.firestore + +import org.junit.Ignore + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreJs +actual typealias IgnoreForAndroidUnitTest = Ignore diff --git a/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index da0c6a584..65d1a0bb7 100644 --- a/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -5,15 +5,9 @@ @file:JvmName("tests") package dev.gitlive.firebase.firestore -import kotlinx.coroutines.CoroutineScope -import org.junit.Ignore - actual val emulatorHost: String = "10.0.2.2" actual val context: Any = "" -// Tests are to be run on AndroidInstrumentedTests. -// Kotlin 1.8 does not allow us to remove the commonTest dependency from AndroidUnitTest -// Therefore we just wont run them -// Kotlin 1.9 will introduce methods for disabling tests properly -actual typealias IgnoreForAndroidUnitTest = Ignore +actual fun encodedAsMap(encoded: Any?): Map = encoded as Map +actual fun Map.asEncoded(): Any = this diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt new file mode 100644 index 000000000..00cae66af --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt @@ -0,0 +1,17 @@ +package dev.gitlive.firebase.firestore + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred + +@PublishedApi +internal fun Deferred.convert(converter: (T) -> R): Deferred { + val deferred = CompletableDeferred() + invokeOnCompletion { exception -> + if (exception == null) { + deferred.complete(converter(getCompleted())) + } else { + deferred.completeExceptionally(exception) + } + } + return deferred +} 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 new file mode 100644 index 000000000..038edc434 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt @@ -0,0 +1,20 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.FirebaseEncoder +import dev.gitlive.firebase.SpecialValueSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException + +/** + * A serializer for [DocumentReference]. If used with [FirebaseEncoder] performs serialization using native Firebase mechanisms. + */ +object DocumentReferenceSerializer : KSerializer by SpecialValueSerializer( + serialName = "DocumentReference", + toNativeValue = DocumentReference::nativeValue, + fromNativeValue = { value -> + when (value) { + is NativeDocumentReference -> DocumentReference(value) + else -> throw SerializationException("Cannot deserialize $value") + } + } +) diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt new file mode 100644 index 000000000..a091c4c05 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt @@ -0,0 +1,17 @@ +package dev.gitlive.firebase.firestore + +import kotlinx.serialization.Serializable + +/** Represents a Firebase FieldValue. */ +@Serializable(with = FieldValueSerializer::class) +expect class FieldValue internal constructor(nativeValue: Any) { + internal val nativeValue: Any + + companion object { + val serverTimestamp: FieldValue + val delete: FieldValue + fun increment(value: Int): FieldValue + fun arrayUnion(vararg elements: Any): FieldValue + fun arrayRemove(vararg elements: Any): FieldValue + } +} 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 new file mode 100644 index 000000000..2dc95492f --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt @@ -0,0 +1,15 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.FirebaseEncoder +import dev.gitlive.firebase.SpecialValueSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException + +/** A serializer for [FieldValue]. Must be used in conjunction with [FirebaseEncoder]. */ +object FieldValueSerializer : KSerializer by SpecialValueSerializer( + serialName = "FieldValue", + toNativeValue = FieldValue::nativeValue, + fromNativeValue = { raw -> + raw?.let(::FieldValue) ?: throw SerializationException("Cannot deserialize $raw") + } +) diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt index 3ef3a7ed3..77093b9cb 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt @@ -1,9 +1,6 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.SpecialValueSerializer -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException /** A class representing a platform specific Firebase GeoPoint. */ expect class NativeGeoPoint @@ -16,15 +13,3 @@ expect class GeoPoint internal constructor(nativeValue: NativeGeoPoint) { val longitude: Double internal val nativeValue: NativeGeoPoint } - -/** Serializer for [GeoPoint]. If used with [FirebaseEncoder] performs serialization using native Firebase mechanisms. */ -object GeoPointSerializer : KSerializer by SpecialValueSerializer( - serialName = "GeoPoint", - toNativeValue = GeoPoint::nativeValue, - fromNativeValue = { value -> - when (value) { - is NativeGeoPoint -> GeoPoint(value) - else -> throw SerializationException("Cannot deserialize $value") - } - } -) 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 new file mode 100644 index 000000000..221456628 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt @@ -0,0 +1,17 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.SpecialValueSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException + +/** Serializer for [GeoPoint]. If used with [FirebaseEncoder] performs serialization using native Firebase mechanisms. */ +object GeoPointSerializer : KSerializer by SpecialValueSerializer( + serialName = "GeoPoint", + toNativeValue = GeoPoint::nativeValue, + fromNativeValue = { value -> + when (value) { + is NativeGeoPoint -> GeoPoint(value) + else -> throw SerializationException("Cannot deserialize $value") + } + } +) \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt index 8a7b02b5c..c6df088af 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt @@ -1,12 +1,6 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.FirebaseDecoder -import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer -import dev.gitlive.firebase.firestore.DoubleAsTimestampSerializer.serverTimestamp -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.nanoseconds @@ -46,67 +40,3 @@ fun Timestamp.toDuration(): Duration = seconds.seconds + nanoseconds.nanoseconds fun Timestamp.Companion.fromMilliseconds(milliseconds: Double): Timestamp = fromDuration(milliseconds.milliseconds) fun Timestamp.toMilliseconds(): Double = toDuration().toDouble(DurationUnit.MILLISECONDS) - -/** A serializer for [BaseTimestamp]. Must be used with [FirebaseEncoder]/[FirebaseDecoder]. */ -object BaseTimestampSerializer : KSerializer by SpecialValueSerializer( - serialName = "Timestamp", - toNativeValue = { value -> - when (value) { - Timestamp.ServerTimestamp -> FieldValue.serverTimestamp.nativeValue - is Timestamp -> value.nativeValue - else -> throw SerializationException("Cannot serialize $value") - } - }, - fromNativeValue = { value -> - when (value) { - is NativeTimestamp -> Timestamp(value) - FieldValue.serverTimestamp.nativeValue -> Timestamp.ServerTimestamp - else -> throw SerializationException("Cannot deserialize $value") - } - } -) - -/** A serializer for [Timestamp]. Must be used with [FirebaseEncoder]/[FirebaseDecoder]. */ -object TimestampSerializer : KSerializer by SpecialValueSerializer( - serialName = "Timestamp", - toNativeValue = Timestamp::nativeValue, - fromNativeValue = { value -> - when (value) { - is NativeTimestamp -> Timestamp(value) - else -> throw SerializationException("Cannot deserialize $value") - } - } -) - -/** A serializer for [Timestamp.ServerTimestamp]. Must be used with [FirebaseEncoder]/[FirebaseDecoder]. */ -object ServerTimestampSerializer : KSerializer by SpecialValueSerializer( - serialName = "Timestamp", - toNativeValue = { FieldValue.serverTimestamp.nativeValue }, - fromNativeValue = { value -> - when (value) { - FieldValue.serverTimestamp.nativeValue -> Timestamp.ServerTimestamp - else -> throw SerializationException("Cannot deserialize $value") - } - } -) - -/** A serializer for a Double field which is stored as a Timestamp. */ -object DoubleAsTimestampSerializer : KSerializer by SpecialValueSerializer( - serialName = "Timestamp", - toNativeValue = { value -> - when(value) { - serverTimestamp -> FieldValue.serverTimestamp.nativeValue - else -> Timestamp.fromMilliseconds(value).nativeValue - } - }, - fromNativeValue = { value -> - when(value) { - FieldValue.serverTimestamp.nativeValue -> serverTimestamp - is NativeTimestamp -> Timestamp(value).toMilliseconds() - is Double -> value - else -> throw SerializationException("Cannot deserialize $value") - } - } -) { - const val serverTimestamp = Double.POSITIVE_INFINITY -} 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 new file mode 100644 index 000000000..92fe32f17 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt @@ -0,0 +1,71 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.firestore.* +import dev.gitlive.firebase.firestore.DoubleAsTimestampSerializer.serverTimestamp +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException + +/** A serializer for [BaseTimestamp]. Must be used with [FirebaseEncoder]/[FirebaseDecoder]. */ +object BaseTimestampSerializer : KSerializer by SpecialValueSerializer( + serialName = "Timestamp", + toNativeValue = { value -> + when (value) { + Timestamp.ServerTimestamp -> FieldValue.serverTimestamp.nativeValue + is Timestamp -> value.nativeValue + else -> throw SerializationException("Cannot serialize $value") + } + }, + fromNativeValue = { value -> + when (value) { + is NativeTimestamp -> Timestamp(value) + FieldValue.serverTimestamp.nativeValue -> Timestamp.ServerTimestamp + else -> throw SerializationException("Cannot deserialize $value") + } + } +) + +/** A serializer for [Timestamp]. Must be used with [FirebaseEncoder]/[FirebaseDecoder]. */ +object TimestampSerializer : KSerializer by SpecialValueSerializer( + serialName = "Timestamp", + toNativeValue = Timestamp::nativeValue, + fromNativeValue = { value -> + when (value) { + is NativeTimestamp -> Timestamp(value) + else -> throw SerializationException("Cannot deserialize $value") + } + } +) + +/** A serializer for [Timestamp.ServerTimestamp]. Must be used with [FirebaseEncoder]/[FirebaseDecoder]. */ +object ServerTimestampSerializer : KSerializer by SpecialValueSerializer( + serialName = "Timestamp", + toNativeValue = { FieldValue.serverTimestamp.nativeValue }, + fromNativeValue = { value -> + when (value) { + FieldValue.serverTimestamp.nativeValue -> Timestamp.ServerTimestamp + else -> throw SerializationException("Cannot deserialize $value") + } + } +) + +/** A serializer for a Double field which is stored as a Timestamp. */ +object DoubleAsTimestampSerializer : KSerializer by SpecialValueSerializer( + serialName = "Timestamp", + toNativeValue = { value -> + when(value) { + serverTimestamp -> FieldValue.serverTimestamp.nativeValue + else -> Timestamp.fromMilliseconds(value).nativeValue + } + }, + fromNativeValue = { value -> + when(value) { + FieldValue.serverTimestamp.nativeValue -> serverTimestamp + is NativeTimestamp -> Timestamp(value).toMilliseconds() + is Double -> value + else -> throw SerializationException("Cannot deserialize $value") + } + } +) { + const val serverTimestamp = Double.POSITIVE_INFINITY +} \ No newline at end of file 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 new file mode 100644 index 000000000..21e9bbc64 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt @@ -0,0 +1,15 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.EncodeSettings + +/** @return whether value is special and shouldn't be encoded/decoded. */ +@PublishedApi +internal expect fun isSpecialValue(value: Any): Boolean + +@PublishedApi +internal inline fun encode(value: T, encodeSettings: EncodeSettings) = + if (value?.let(::isSpecialValue) == true) { + value + } else { + dev.gitlive.firebase.encode(value, encodeSettings) + } 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 8dc81fedc..f5c62358d 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 @@ -5,12 +5,14 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.* +import kotlinx.coroutines.Deferred import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationStrategy +import kotlin.jvm.JvmName /** Returns the [FirebaseFirestore] instance of the default [FirebaseApp]. */ expect val Firebase.firestore: FirebaseFirestore @@ -32,22 +34,41 @@ expect class FirebaseFirestore { suspend fun enableNetwork() } -expect class Transaction { +sealed class SetOptions { + object Merge : SetOptions() + object Overwrite : SetOptions() + data class MergeFields(val fields: List) : SetOptions() + data class MergeFieldPaths(val fieldPaths: List) : SetOptions() { + val encodedFieldPaths = fieldPaths.map { it.encoded } + } +} + +abstract class BaseTransaction { - fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, merge: Boolean = false): Transaction - fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, vararg mergeFields: String): Transaction - fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, vararg mergeFieldPaths: FieldPath): Transaction + fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, merge: Boolean = false): Transaction - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, vararg mergeFields: String): Transaction - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, vararg mergeFieldPaths: FieldPath): Transaction + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true): Transaction - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true): Transaction + protected abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseTransaction - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair): Transaction - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair): Transaction + fun update(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(data, encodeSettings)!!) + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!) + + @JvmName("updateFields") + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + @JvmName("updateFieldPaths") + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + + protected abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction + protected abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction + protected abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction +} +expect class Transaction : BaseTransaction { fun delete(documentRef: DocumentReference): Transaction suspend fun get(documentRef: DocumentReference): DocumentSnapshot } @@ -110,31 +131,134 @@ fun Query.endBefore(vararg fieldValues: Any) = _endBefore(*(fieldValues.map { it fun Query.endAt(document: DocumentSnapshot) = _endAt(document) fun Query.endAt(vararg fieldValues: Any) = _endAt(*(fieldValues.map { it.value }.toTypedArray())) -expect class WriteBatch { - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, merge: Boolean = false): WriteBatch - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, vararg mergeFields: String): WriteBatch - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, vararg mergeFieldPaths: FieldPath): WriteBatch - - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, merge: Boolean = false): WriteBatch - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, vararg mergeFields: String): WriteBatch - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, vararg mergeFieldPaths: FieldPath): WriteBatch - - inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true): WriteBatch - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true): WriteBatch +abstract class BaseWriteBatch { + inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = + setEncoded(documentRef, encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = + setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = + setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = + setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String)= + setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = + setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false, vararg fieldsAndValues: Pair) = + setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty(), merge) + + abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseWriteBatch + abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, encodedFieldsAndValues: List>, merge: Boolean): BaseWriteBatch + + inline fun update(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = + updateEncoded(documentRef, encode(data, encodeSettings)!!) + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = + updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!) + inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg fieldsAndValues: Pair) = + updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + + @JvmName("updateField") + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + @JvmName("updateFieldPath") + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + + abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch + abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any, encodedFieldsAndValues: List>): BaseWriteBatch + + protected abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch + protected abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch +} - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair): WriteBatch - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair): WriteBatch +expect class WriteBatch : BaseWriteBatch { + val async: Async fun delete(documentRef: DocumentReference): WriteBatch suspend fun commit() + + @Suppress("DeferredIsResult") + class Async { + fun commit(): Deferred + } } /** A class representing a platform specific Firebase DocumentReference. */ expect class NativeDocumentReference +abstract class BaseDocumentReference { + + abstract class Async { + inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = setEncoded(encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = setEncoded(encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = setEncoded(encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + + fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = setEncoded( + encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String)= setEncoded( + encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = setEncoded( + encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + + abstract fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred + + inline fun update(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(encode(data, encodeSettings)!!) + fun update(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = update(encode(strategy, data, encodeSettings)) + + @JvmName("updateFields") + fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + @JvmName("updateFieldPaths") + fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + + abstract fun updateEncoded(encodedData: Any): Deferred + protected abstract fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred + protected abstract fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred + + abstract fun delete(): Deferred + } + + abstract val async: Async + + suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = + async.set(data, encodeSettings, merge).await() + + suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = + async.set(data, encodeSettings, mergeFields = mergeFields).await() + + suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = + async.set(data, encodeSettings, mergeFieldPaths = mergeFieldPaths).await() + + suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = + async.set(strategy, data, encodeSettings, merge).await() + + suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = + async.set(strategy, data, encodeSettings, mergeFields = mergeFields).await() + + suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = + async.set(strategy, data, encodeSettings, mergeFieldPaths = mergeFieldPaths).await() + + @Suppress("UNCHECKED_CAST") + suspend inline fun update(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = + async.update(data, encodeSettings).await() + + @Suppress("UNCHECKED_CAST") + suspend fun update(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = + async.update(strategy, data, encodeSettings).await() + + @JvmName("updateFields") + suspend fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = + async.update(fieldsAndValues = fieldsAndValues, encodeSettings).await() + + @JvmName("updateFieldPaths") + suspend fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = + async.update(fieldsAndValues = fieldsAndValues, encodeSettings).await() + + suspend fun delete() = + async.delete().await() +} + /** A class representing a Firebase DocumentReference. */ @Serializable(with = DocumentReferenceSerializer::class) -expect class DocumentReference internal constructor(nativeValue: NativeDocumentReference) { +expect class DocumentReference internal constructor(nativeValue: NativeDocumentReference) : BaseDocumentReference { internal val nativeValue: NativeDocumentReference val id: String @@ -145,48 +269,22 @@ expect class DocumentReference internal constructor(nativeValue: NativeDocumentR fun collection(collectionPath: String): CollectionReference suspend fun get(): DocumentSnapshot - - suspend inline fun set(data: T, encodeDefaults: Boolean = true, merge: Boolean = false) - suspend inline fun set(data: T, encodeDefaults: Boolean = true, vararg mergeFields: String) - suspend inline fun set(data: T, encodeDefaults: Boolean = true, vararg mergeFieldPaths: FieldPath) - - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, merge: Boolean = false) - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, vararg mergeFields: String) - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, vararg mergeFieldPaths: FieldPath) - - suspend inline fun update(data: T, encodeDefaults: Boolean = true) - suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true) - - suspend fun update(vararg fieldsAndValues: Pair) - suspend fun update(vararg fieldsAndValues: Pair) - - suspend fun delete() } -/** - * A serializer for [DocumentReference]. If used with [FirebaseEncoder] performs serialization using native Firebase mechanisms. - */ -object DocumentReferenceSerializer : KSerializer by SpecialValueSerializer( - serialName = "DocumentReference", - toNativeValue = DocumentReference::nativeValue, - fromNativeValue = { value -> - when (value) { - is NativeDocumentReference -> DocumentReference(value) - else -> throw SerializationException("Cannot deserialize $value") - } - } -) - expect class CollectionReference : Query { val path: String + val async: Async val document: DocumentReference val parent: DocumentReference? fun document(documentPath: String): DocumentReference - suspend inline fun add(data: T, encodeDefaults: Boolean = true): DocumentReference - @Deprecated("This will be replaced with add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true)") - suspend fun add(data: T, strategy: SerializationStrategy, encodeDefaults: Boolean = true): DocumentReference - suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true): DocumentReference + suspend inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()): DocumentReference + suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): DocumentReference + @Suppress("DeferredIsResult") + class Async { + inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()): Deferred + fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): Deferred + } } expect class FirebaseFirestoreException : FirebaseException @@ -241,12 +339,12 @@ expect class DocumentChange { expect class DocumentSnapshot { inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T - fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T + fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings(), serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T fun contains(field: String): Boolean inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T - fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T + fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings(), serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T val exists: Boolean val id: String @@ -267,27 +365,7 @@ expect class SnapshotMetadata { expect class FieldPath(vararg fieldNames: String) { val documentId: FieldPath + val encoded: EncodedFieldPath } -/** Represents a Firebase FieldValue. */ -@Serializable(with = FieldValueSerializer::class) -expect class FieldValue internal constructor(nativeValue: Any) { - internal val nativeValue: Any - - companion object { - val serverTimestamp: FieldValue - val delete: FieldValue - fun increment(value: Int): FieldValue - fun arrayUnion(vararg elements: Any): FieldValue - fun arrayRemove(vararg elements: Any): FieldValue - } -} - -/** A serializer for [FieldValue]. Must be used in conjunction with [FirebaseEncoder]. */ -object FieldValueSerializer : KSerializer by SpecialValueSerializer( - serialName = "FieldValue", - toNativeValue = FieldValue::nativeValue, - fromNativeValue = { raw -> - raw?.let(::FieldValue) ?: throw SerializationException("Cannot deserialize $raw") - } -) +expect class EncodedFieldPath diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt index 807ee34bb..269f8c4f1 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt @@ -1,18 +1,35 @@ package dev.gitlive.firebase.firestore +import dev.gitlive.firebase.EncodeSettings +import kotlin.jvm.JvmName + +//** Helper method to perform an update operation. */ +@JvmName("performUpdateFields") +fun encodeFieldAndValue( + fieldsAndValues: Array>, + encodeSettings: EncodeSettings +) = encodeFieldAndValue(fieldsAndValues, encodeField = { it }, encodeValue = { encode(it, encodeSettings) }) + +/** Helper method to perform an update operation. */ +@JvmName("performUpdateFieldPaths") +fun encodeFieldAndValue( + fieldsAndValues: Array>, + encodeSettings: EncodeSettings, +) = encodeFieldAndValue(fieldsAndValues, { it.encoded }, { encode(it, encodeSettings) }) + /** Helper method to perform an update operation in Android and JS. */ -internal fun performUpdate( +internal fun encodeFieldAndValue( fieldsAndValues: Array>, encodeField: (T) -> K, - encodeValue: (Any?) -> Any?, - update: (K, Any?, Array) -> R -) : R? = + encodeValue: (Any?) -> Any? +) : List>? = fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } ?.map { (field, value) -> encodeField(field) to value?.let { encodeValue(it) } } - ?.let { encoded -> - update( - encoded[0].first, - encoded[0].second, - encoded.drop(1).flatMap { (field, value) -> listOf(field, value) }.toTypedArray() - ) - } + +internal fun List>.performUpdate( + update: (K, Any?, Array) -> R +) = update( + this[0].first, + this[0].second, + this.drop(1).flatMap { (field, value) -> listOf(field, value) }.toTypedArray() +) 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 new file mode 100644 index 000000000..5916bce4c --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/serializers.kt @@ -0,0 +1,28 @@ +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/DeferredExtensionsTest.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt new file mode 100644 index 000000000..147a1c9d8 --- /dev/null +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt @@ -0,0 +1,42 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.runTest +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class DeferredExtensionsTest { + + @Test + fun testConvert() = runTest { + val original = CompletableDeferred() + val converted = original.convert { it.toString() } + + original.complete(1) + assertEquals("1", converted.await()) + } + + @Test + fun testConvertCompleted() = runTest { + val original = CompletableDeferred(1) + val converted = original.convert { it.toString() } + + assertEquals("1", converted.await()) + } + + @Test + fun testCanceled() = runTest { + val original = CompletableDeferred() + val converted = original.convert { it.toString() } + + val result = runCatching { + original.cancel() + + converted.await() + } + assertTrue(result.isFailure) + assertTrue(result.exceptionOrNull() is CancellationException) + } +} 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 9ed088172..ad4b5374b 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 @@ -14,4 +14,10 @@ class FieldValueTests { assertNotEquals(FieldValue.delete, FieldValue.serverTimestamp) // Note: arrayUnion and arrayRemove can't be checked due to vararg to array conversion } + + @Test + @IgnoreJs + fun serializers() = runTest { + assertEquals(FieldValueSerializer, FieldValue.delete.firebaseSerializer()) + } } diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt new file mode 100644 index 000000000..4641fdbac --- /dev/null +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt @@ -0,0 +1,84 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.* +import kotlinx.serialization.Serializable +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +@IgnoreJs +@IgnoreForAndroidUnitTest +class FirestoreAsync { + + lateinit var firestore: FirebaseFirestore + + @BeforeTest + fun initializeFirebase() { + val app = Firebase.apps(context).firstOrNull() ?: Firebase.initialize( + context, + FirebaseOptions( + applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", + apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", + databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", + storageBucket = "fir-kotlin-sdk.appspot.com", + projectId = "fir-kotlin-sdk", + gcmSenderId = "846484016111" + ) + ) + + firestore = Firebase.firestore(app).apply { + useEmulator(emulatorHost, 8080) + setSettings(persistenceEnabled = false) + } + } + + @AfterTest + fun deinitializeFirebase() = runBlockingTest { + Firebase.apps(context).forEach { + it.delete() + } + } + + @Serializable + data class TestData(val value: Int) + + @Test + fun asyncDocumentReferenceTest() = runTest { + fun getDocument() = firestore.collection("asyncDocumentReferenceTest") + .document("asyncDocumentReferenceTest") + + firestore.disableNetwork() + val update1 = getDocument().async.set(TestData(1)) + val update2 = getDocument().async.update(TestData(2)) + firestore.enableNetwork() + update1.await() + update2.await() + } + + @Test + fun asyncBatchTest() = runTest { + val batch = firestore.batch() + + fun getDocument() = firestore.collection("asyncBatchTest") + .document("asyncBatchTest") + + firestore.disableNetwork() + batch.set(getDocument(), TestData(1)) + batch.update(getDocument(), TestData(1)) + + val result = batch.async.commit() + firestore.enableNetwork() + + result.await() + } + + @Test + fun asyncCollectionAddTest() = runTest { + firestore.disableNetwork() + val result = firestore.collection("asyncCollectionAddTest").async + .add(TestData(1)) + firestore.enableNetwork() + + result.await() + } +} 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 45457040c..b6d2bcc26 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,10 +1,6 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode -import dev.gitlive.firebase.nativeAssertEquals -import dev.gitlive.firebase.nativeMapOf -import dev.gitlive.firebase.runTest +import dev.gitlive.firebase.* import kotlinx.serialization.Serializable import kotlin.test.Test import kotlin.test.assertEquals @@ -22,23 +18,28 @@ class GeoPointTests { fun encodeGeoPointObject() = runTest { val geoPoint = GeoPoint(12.3, 45.6) val item = TestDataWithGeoPoint("123", geoPoint) + val encoded = encodedAsMap(encode(item, shouldEncodeElementDefault = false)) + assertEquals("123", encoded["uid"]) // check GeoPoint is encoded to a platform representation - nativeAssertEquals( - nativeMapOf("uid" to "123", "location" to geoPoint.nativeValue), - encode(item, shouldEncodeElementDefault = false) - ) + assertEquals(geoPoint.nativeValue, encoded["location"]) } @Test fun decodeGeoPointObject() = runTest { val geoPoint = GeoPoint(12.3, 45.6) - val obj = nativeMapOf( + val obj = mapOf( "uid" to "123", "location" to geoPoint.nativeValue - ) + ).asEncoded() val decoded: TestDataWithGeoPoint = decode(obj) assertEquals("123", decoded.uid) // check a platform GeoPoint is properly wrapped assertEquals(geoPoint, decoded.location) } + + @Test + @IgnoreJs + fun serializers() = runTest { + assertEquals(GeoPointSerializer, GeoPoint(0.0,0.0).firebaseSerializer()) + } } diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt new file mode 100644 index 000000000..c98152a85 --- /dev/null +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -0,0 +1,5 @@ +package dev.gitlive.firebase.firestore + +/** Marker annotation to ignore JS test. */ +expect annotation class IgnoreJs() +expect annotation class IgnoreForAndroidUnitTest() 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 8b2956682..08d10b89e 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 @@ -4,12 +4,8 @@ package dev.gitlive.firebase.firestore -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 dev.gitlive.firebase.* +import dev.gitlive.firebase.firestore.encode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -19,7 +15,9 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.TestResult import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer import kotlin.random.Random import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -32,7 +30,11 @@ import kotlin.test.assertTrue expect val emulatorHost: String expect val context: Any -expect annotation class IgnoreForAndroidUnitTest() + +/** @return a map extracted from the encoded data. */ +expect fun encodedAsMap(encoded: Any?): Map +/** @return pairs as raw encoded data. */ +expect fun Map.asEncoded(): Any @IgnoreForAndroidUnitTest class FirebaseFirestoreTest { @@ -159,6 +161,11 @@ class FirebaseFirestoreTest { val doc = firestore .collection("testServerTimestampFieldValue") .document("test") + doc.set( + FirestoreTimeTest.serializer(), + FirestoreTimeTest("ServerTimestamp", Timestamp(123, 0)), + ) + assertEquals(Timestamp(123, 0), doc.get().get("time", TimestampSerializer)) doc.set(FirestoreTimeTest.serializer(), FirestoreTimeTest("ServerTimestamp", Timestamp.ServerTimestamp)) @@ -184,7 +191,31 @@ class FirebaseFirestoreTest { val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() assertTrue(pendingWritesSnapshot.metadata.hasPendingWrites) - assertNull(pendingWritesSnapshot.get("time", BaseTimestamp.serializer().nullable, ServerTimestampBehavior.NONE)) + assertNull(pendingWritesSnapshot.get("time", BaseTimestamp.serializer().nullable, serverTimestampBehavior = ServerTimestampBehavior.NONE)) + } + + @Test + fun testExtendedSetBatch() = runTest { + val doc = firestore + .collection("testServerTestSetBatch") + .document("test") + val batch = firestore.batch() + batch.set( + documentRef = doc, + strategy = FirestoreTest.serializer(), + data = FirestoreTest( + prop1 = "prop1", + time = 123.0 + ), + fieldsAndValues = arrayOf( + "time" to 124.0 + ) + ) + batch.commit() + + assertEquals(124.0, doc.get().get("time")) + assertEquals("prop1", doc.get().data(FirestoreTest.serializer()).prop1) + } @Test @@ -202,8 +233,8 @@ class FirebaseFirestoreTest { val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() assertTrue(pendingWritesSnapshot.metadata.hasPendingWrites) - assertNotNull(pendingWritesSnapshot.get("time", BaseTimestamp.serializer().nullable, ServerTimestampBehavior.ESTIMATE)) - assertNotEquals(Timestamp.ServerTimestamp, pendingWritesSnapshot.data(FirestoreTimeTest.serializer(), ServerTimestampBehavior.ESTIMATE).time) + assertNotNull(pendingWritesSnapshot.get("time", ServerTimestampBehavior.ESTIMATE)) + assertNotEquals(Timestamp.ServerTimestamp, pendingWritesSnapshot.data(FirestoreTimeTest.serializer(), serverTimestampBehavior = ServerTimestampBehavior.ESTIMATE).time) } @Test @@ -221,7 +252,7 @@ class FirebaseFirestoreTest { val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() assertTrue(pendingWritesSnapshot.metadata.hasPendingWrites) - assertNull(pendingWritesSnapshot.get("time", BaseTimestamp.serializer().nullable, ServerTimestampBehavior.PREVIOUS)) + assertNull(pendingWritesSnapshot.get("time", BaseTimestamp.serializer().nullable, serverTimestampBehavior = ServerTimestampBehavior.PREVIOUS)) } @Test @@ -442,6 +473,89 @@ class FirebaseFirestoreTest { assertEquals(listOf("first"), dataAfter.list) } + @Test + fun testSetBatchDoesNotEncodeEmptyValues() = runTest { + val doc = firestore + .collection("testServerTestSetBatch") + .document("test") + val batch = firestore.batch() + batch.set( + documentRef = doc, + strategy = FirestoreTest.serializer(), + data = FirestoreTest( + prop1 = "prop1-set", + time = 125.0 + ), + fieldsAndValues = arrayOf>() + ) + batch.commit() + + assertEquals(125.0, doc.get().get("time") as Double?) + assertEquals("prop1-set", doc.get().data(FirestoreTest.serializer()).prop1) + } + + @Test + fun testExtendedUpdateBatch() = runTest { + val doc = firestore + .collection("testServerTestSetBatch") + .document("test").apply { + set( + FirestoreTest( + prop1 = "prop1", + time = 123.0 + ) + ) + } + + + val batch = firestore.batch() + batch.update( + documentRef = doc, + strategy = FirestoreTest.serializer(), + data = FirestoreTest( + prop1 = "prop1-updated", + time = 123.0 + ), + encodeSettings = EncodeSettings(shouldEncodeElementDefault = false), + fieldsAndValues = arrayOf( + "time" to FieldValue.delete + ) + ) + batch.commit() + + assertEquals(null, doc.get().get("time") as Double?) + assertEquals("prop1-updated", doc.get().data(FirestoreTest.serializer()).prop1) + } + + @Test + fun testUpdateBatchDoesNotEncodeEmptyValues() = runTest { + val doc = firestore + .collection("testServerTestSetBatch") + .document("test").apply { + set( + FirestoreTest( + prop1 = "prop1", + time = 123.0 + ) + ) + } + val batch = firestore.batch() + batch.update( + documentRef = doc, + strategy = FirestoreTest.serializer(), + data = FirestoreTest( + prop1 = "prop1-set", + time = 126.0 + ), + encodeSettings = EncodeSettings(shouldEncodeElementDefault = false), + fieldsAndValues = arrayOf>() + ) + batch.commit() + + assertEquals(126.0, doc.get().get("time") as Double?) + assertEquals("prop1-set", doc.get().data(FirestoreTest.serializer()).prop1) + } + @Test fun testLegacyDoubleTimestamp() = runTest { @Serializable @@ -463,8 +577,8 @@ class FirebaseFirestoreTest { val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() assertTrue(pendingWritesSnapshot.metadata.hasPendingWrites) - assertNotNull(pendingWritesSnapshot.get("time", DoubleAsTimestampSerializer, ServerTimestampBehavior.ESTIMATE )) - assertNotEquals(DoubleAsTimestampSerializer.serverTimestamp, pendingWritesSnapshot.data(DoubleTimestamp.serializer(), ServerTimestampBehavior.ESTIMATE).time) + assertNotNull(pendingWritesSnapshot.get("time", DoubleAsTimestampSerializer, serverTimestampBehavior = ServerTimestampBehavior.ESTIMATE )) + assertNotEquals(DoubleAsTimestampSerializer.serverTimestamp, pendingWritesSnapshot.data(DoubleTimestamp.serializer(), serverTimestampBehavior = ServerTimestampBehavior.ESTIMATE).time) } @Test @@ -525,6 +639,143 @@ class FirebaseFirestoreTest { assertEquals(setOf(DocumentWithTimestamp(futureTimestamp)), gtQueryResult) } + + @Test + fun testGeoPointSerialization() = runTest { + @Serializable + data class DataWithGeoPoint(val geoPoint: GeoPoint) + + fun getDocument() = firestore.collection("geoPointSerialization") + .document("geoPointSerialization") + + val data = DataWithGeoPoint(GeoPoint(12.34, 56.78)) + // store geo point + getDocument().set(DataWithGeoPoint.serializer(), data) + // restore data + val savedData = getDocument().get().data(DataWithGeoPoint.serializer()) + assertEquals(data.geoPoint, savedData.geoPoint) + + // update data + val updatedData = DataWithGeoPoint(GeoPoint(87.65, 43.21)) + getDocument().update(FieldPath(DataWithGeoPoint::geoPoint.name) to updatedData.geoPoint) + // verify update + val updatedSavedData = getDocument().get().data(DataWithGeoPoint.serializer()) + assertEquals(updatedData.geoPoint, updatedSavedData.geoPoint) + } + + @Test + fun testDocumentReferenceSerialization() = runTest { + @Serializable + data class DataWithDocumentReference( + val documentReference: DocumentReference + ) + + fun getCollection() = firestore.collection("documentReferenceSerialization") + fun getDocument() = getCollection() + .document("documentReferenceSerialization") + val documentRef1 = getCollection().document("refDoc1").apply { + set(mapOf("value" to 1)) + } + val documentRef2 = getCollection().document("refDoc2").apply { + set(mapOf("value" to 2)) + } + + val data = DataWithDocumentReference(documentRef1) + // store reference + getDocument().set(DataWithDocumentReference.serializer(), data) + // restore data + val savedData = getDocument().get().data(DataWithDocumentReference.serializer()) + assertEquals(data.documentReference.path, savedData.documentReference.path) + + // update data + val updatedData = DataWithDocumentReference(documentRef2) + getDocument().update( + FieldPath(DataWithDocumentReference::documentReference.name) to updatedData.documentReference.withSerializer(DocumentReferenceSerializer) + ) + // verify update + val updatedSavedData = getDocument().get().data(DataWithDocumentReference.serializer()) + assertEquals(updatedData.documentReference.path, updatedSavedData.documentReference.path) + } + + @Serializable + data class TestDataWithDocumentReference( + val uid: String, + val reference: DocumentReference, + val optionalReference: DocumentReference? + ) + + @Serializable + data class TestDataWithOptionalDocumentReference( + val optionalReference: DocumentReference? + ) + + @Test + fun encodeDocumentReference() = runTest { + val doc = firestore.document("a/b") + val item = TestDataWithDocumentReference("123", doc, doc) + val encoded = encodedAsMap(encode(item, shouldEncodeElementDefault = false)) + assertEquals("123", encoded["uid"]) + assertEquals(doc.nativeValue, encoded["reference"]) + assertEquals(doc.nativeValue, encoded["optionalReference"]) + } + + @Test + fun encodeNullDocumentReference() = runTest { + val item = TestDataWithOptionalDocumentReference(null) + val encoded = encodedAsMap(encode(item, shouldEncodeElementDefault = false)) + assertNull(encoded["optionalReference"]) + } + + @Test + fun decodeDocumentReference() = runTest { + val doc = firestore.document("a/b") + val obj = mapOf( + "uid" to "123", + "reference" to doc.nativeValue, + "optionalReference" to doc.nativeValue + ).asEncoded() + val decoded: TestDataWithDocumentReference = decode(obj) + assertEquals("123", decoded.uid) + assertEquals(doc.path, decoded.reference.path) + assertEquals(doc.path, decoded.optionalReference?.path) + } + + @Test + fun decodeNullDocumentReference() = runTest { + val obj = mapOf("optionalReference" to null).asEncoded() + val decoded: TestDataWithOptionalDocumentReference = decode(obj) + assertNull(decoded.optionalReference?.path) + } + + @Test + fun testFieldValuesOps() = runTest { + @Serializable + data class TestData(val values: List) + fun getDocument() = firestore.collection("fieldValuesOps") + .document("fieldValuesOps") + + val data = TestData(listOf(1)) + // store + getDocument().set(TestData.serializer(), data) + // append & verify + getDocument().update(FieldPath(TestData::values.name) to FieldValue.arrayUnion(2)) + + var savedData = getDocument().get().data(TestData.serializer()) + assertEquals(listOf(1, 2), savedData.values) + + // remove & verify + getDocument().update(FieldPath(TestData::values.name) to FieldValue.arrayRemove(1)) + savedData = getDocument().get().data(TestData.serializer()) + assertEquals(listOf(2), savedData.values) + + val list = getDocument().get().get(TestData::values.name, ListSerializer(Int.serializer()).nullable) + assertEquals(listOf(2), list) + // delete & verify + getDocument().update(FieldPath(TestData::values.name) to FieldValue.delete) + val deletedList = getDocument().get().get(TestData::values.name, ListSerializer(Int.serializer()).nullable) + assertNull(deletedList) + } + private suspend fun setupFirestoreData() { firestore.collection("testFirestoreQuerying") .document("one") diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt new file mode 100644 index 000000000..f3c0efd0a --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt @@ -0,0 +1,27 @@ +package dev.gitlive.firebase.firestore + +import cocoapods.FirebaseFirestoreInternal.FIRFieldValue +import kotlinx.serialization.Serializable + +/** A class representing a platform specific Firebase FieldValue. */ +private typealias NativeFieldValue = FIRFieldValue + +/** Represents a Firebase FieldValue. */ +@Serializable(with = FieldValueSerializer::class) +actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { + init { + require(nativeValue is NativeFieldValue) + } + override fun equals(other: Any?): Boolean = + this === other || other is FieldValue && nativeValue == other.nativeValue + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() + + actual companion object { + actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.fieldValueForServerTimestamp()) + actual val delete: FieldValue get() = FieldValue(NativeFieldValue.fieldValueForDelete()) + actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.fieldValueForIntegerIncrement(value.toLong())) + actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.fieldValueForArrayUnion(elements.asList())) + actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.fieldValueForArrayRemove(elements.asList())) + } +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt new file mode 100644 index 000000000..ab06e2eed --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -0,0 +1,12 @@ +package dev.gitlive.firebase.firestore + +import cocoapods.FirebaseFirestoreInternal.FIRFieldValue + +@PublishedApi +internal actual fun isSpecialValue(value: Any) = when(value) { + is FIRFieldValue, + is NativeGeoPoint, + is NativeTimestamp, + is NativeDocumentReference -> true + else -> false +} 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 e7baf2583..a40eca557 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -9,6 +9,7 @@ import cocoapods.FirebaseFirestoreInternal.FIRDocumentChangeType.* import dev.gitlive.firebase.* import kotlinx.cinterop.* import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.runBlocking @@ -72,89 +73,106 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { } @Suppress("UNCHECKED_CAST") -actual class WriteBatch(val ios: FIRWriteBatch) { - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean) = - ios.setData(encode(data, encodeDefaults)!! as Map, documentRef.ios, merge).let { this } - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - ios.setData(encode(data, encodeDefaults)!! as Map, documentRef.ios, mergeFields.asList()).let { this } - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - ios.setData(encode(data, encodeDefaults)!! as Map, documentRef.ios, mergeFieldPaths.map { it.ios }).let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = - ios.setData(encode(strategy, data, encodeDefaults)!! as Map, documentRef.ios, merge).let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - ios.setData(encode(strategy, data, encodeDefaults)!! as Map, documentRef.ios, mergeFields.asList()).let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - ios.setData(encode(strategy, data, encodeDefaults)!! as Map, documentRef.ios, mergeFieldPaths.map { it.ios }).let { this } +actual class WriteBatch(val ios: FIRWriteBatch) : BaseWriteBatch() { + + actual val async = Async(ios) + + override fun setEncoded( + documentRef: DocumentReference, + encodedData: Any, + setOptions: SetOptions + ): BaseWriteBatch = 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 } + + override fun setEncoded( + documentRef: DocumentReference, + encodedData: Any, + encodedFieldsAndValues: List>, + merge: Boolean + ): BaseWriteBatch { + val serializedItem = encodedData as Map + val serializedFieldAndValues = encodedFieldsAndValues.toMap() + + val result = serializedItem + serializedFieldAndValues + ios.setData(result as Map, documentRef.ios, merge) + return this + } - actual inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = - ios.updateData(encode(data, encodeDefaults) as Map, documentRef.ios).let { this } + override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = ios.updateData(encodedData as Map, documentRef.ios).let { this } - actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - ios.updateData(encode(strategy, data, encodeDefaults) as Map, documentRef.ios).let { this } + override fun updateEncoded( + documentRef: DocumentReference, + encodedData: Any, + encodedFieldsAndValues: List> + ): BaseWriteBatch { + val serializedItem = encodedData as Map + val serializedFieldAndValues = encodedFieldsAndValues.toMap() - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - ios.updateData( - fieldsAndValues.associate { (field, value) -> field to encode(value, true) }, - documentRef.ios - ).let { this } + val result = serializedItem + serializedFieldAndValues + return ios.updateData(result as Map, documentRef.ios).let { this } + } - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - ios.updateData( - fieldsAndValues.associate { (path, value) -> path.ios to encode(value, true) }, - documentRef.ios - ).let { this } + override fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseWriteBatch = ios.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + override fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseWriteBatch = 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 suspend fun commit() = async.commit().await() + actual class Async(@PublishedApi internal val ios: FIRWriteBatch) { + actual fun commit() = deferred { ios.commitWithCompletion(it) } + } } @Suppress("UNCHECKED_CAST") -actual class Transaction(val ios: FIRTransaction) { - - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean) = - ios.setData(encode(data, encodeDefaults)!! as Map, documentRef.ios, merge).let { this } - - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String) = - ios.setData(encode(data, encodeDefaults)!! as Map, documentRef.ios, mergeFields.asList()).let { this } - - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - ios.setData(encode(data, encodeDefaults)!! as Map, documentRef.ios, mergeFieldPaths.map { it.ios }).let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = - ios.setData(encode(strategy, data, encodeDefaults)!! as Map, documentRef.ios, merge).let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - ios.setData(encode(strategy, data, encodeDefaults)!! as Map, documentRef.ios, mergeFields.asList()).let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - ios.setData(encode(strategy, data, encodeDefaults)!! as Map, documentRef.ios, mergeFieldPaths.map { it.ios }).let { this } - - actual fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = - ios.updateData(encode(data, encodeDefaults) as Map, documentRef.ios).let { this } - - actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - ios.updateData(encode(strategy, data, encodeDefaults) as Map, documentRef.ios).let { this } - - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - ios.updateData( - fieldsAndValues.associate { (field, value) -> field to encode(value, true) }, - documentRef.ios - ).let { this } - - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - ios.updateData( - fieldsAndValues.associate { (path, value) -> path.ios to encode(value, true) }, - documentRef.ios - ).let { this } +actual class Transaction(val ios: FIRTransaction) : BaseTransaction() { + + override fun setEncoded( + documentRef: DocumentReference, + encodedData: Any, + setOptions: SetOptions + ): BaseTransaction = 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 } + + override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = ios.updateData(encodedData as Map, documentRef.ios).let { this } + + override fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseTransaction = ios.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + override fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseTransaction = ios.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } actual fun delete(documentRef: DocumentReference) = ios.deleteDocument(documentRef.ios).let { this } @@ -167,63 +185,59 @@ actual class Transaction(val ios: FIRTransaction) { /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReference = FIRDocumentReference +@Suppress("UNCHECKED_CAST") @Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) { - val ios: NativeDocumentReference by ::nativeValue +actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { - actual val id: String - get() = ios.documentID - - actual val path: String - get() = ios.path + class Async(@PublishedApi internal val ios: NativeDocumentReference) : BaseDocumentReference.Async() { - actual val parent: CollectionReference - get() = CollectionReference(ios.parent) - - actual fun collection(collectionPath: String) = CollectionReference(ios.collectionWithPath(collectionPath)) + override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = deferred { + 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 inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean) = - await { ios.setData(encode(data, encodeDefaults)!! as Map, merge, it) } + override fun updateEncoded(encodedData: Any): Deferred = deferred { + ios.updateData(encodedData as Map, it) + } - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - await { ios.setData(encode(data, encodeDefaults)!! as Map, mergeFields.asList(), it) } + override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = deferred { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - await { ios.setData(encode(data, encodeDefaults)!! as Map, mergeFieldPaths.map { it.ios }, it) } + override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = deferred { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = - await { ios.setData(encode(strategy, data, encodeDefaults)!! as Map, merge, it) } + override fun delete() = + deferred { ios.deleteDocumentWithCompletion(it) } + } - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - await { ios.setData(encode(strategy, data, encodeDefaults)!! as Map, mergeFields.asList(), it) } + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> + snapshot?.let { trySend(DocumentSnapshot(snapshot)) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - await { ios.setData(encode(strategy, data, encodeDefaults)!! as Map, mergeFieldPaths.map { it.ios }, it) } + val ios: NativeDocumentReference by ::nativeValue - actual suspend inline fun update(data: T, encodeDefaults: Boolean) = - await { ios.updateData(encode(data, encodeDefaults) as Map, it) } + actual val id: String + get() = ios.documentID - actual suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - await { ios.updateData(encode(strategy, data, encodeDefaults) as Map, it) } + actual val path: String + get() = ios.path - actual suspend fun update(vararg fieldsAndValues: Pair) = - await { block -> - ios.updateData( - fieldsAndValues.associate { (field, value) -> field to encode(value, true) }, - block - ) - } + actual val parent: CollectionReference + get() = CollectionReference(ios.parent) - actual suspend fun update(vararg fieldsAndValues: Pair) = - await { block -> - ios.updateData( - fieldsAndValues.associate { (path, value) -> path.ios to encode(value, true) }, - block - ) - } + override val async = Async(nativeValue) - actual suspend fun delete() = - await { ios.deleteDocumentWithCompletion(it) } + actual fun collection(collectionPath: String) = CollectionReference(ios.collectionWithPath(collectionPath)) actual suspend fun get() = DocumentSnapshot(awaitResult { ios.getDocumentWithCompletion(it) }) @@ -236,14 +250,6 @@ actual class DocumentReference actual constructor(internal actual val nativeValu awaitClose { listener.remove() } } - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> - snapshot?.let { trySend(DocumentSnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - override fun equals(other: Any?): Boolean = this === other || other is DocumentReference && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() @@ -320,25 +326,35 @@ actual open class Query(open val ios: FIRQuery) { internal actual fun _endAt(vararg fieldValues: Any) = Query(ios.queryEndingAtValues(fieldValues.asList())) } + @Suppress("UNCHECKED_CAST") actual class CollectionReference(override val ios: FIRCollectionReference) : Query(ios) { actual val path: String get() = ios.path + actual val async = Async(ios) + actual val document get() = DocumentReference(ios.documentWithAutoID()) actual val parent get() = ios.parent?.let{DocumentReference(it)} actual fun document(documentPath: String) = DocumentReference(ios.documentWithPath(documentPath)) - actual suspend inline fun add(data: T, encodeDefaults: Boolean) = - DocumentReference(await { ios.addDocumentWithData(encode(data, encodeDefaults) as Map, it) }) + actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = + DocumentReference(await { ios.addDocumentWithData(encode(data, encodeSettings) as Map, it) }) + actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = + DocumentReference(await { ios.addDocumentWithData(encode(strategy, data, encodeSettings) as Map, it) }) - actual suspend fun add(data: T, strategy: SerializationStrategy, encodeDefaults: Boolean) = - DocumentReference(await { ios.addDocumentWithData(encode(strategy, data, encodeDefaults) as Map, it) }) - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - DocumentReference(await { ios.addDocumentWithData(encode(strategy, data, encodeDefaults) as Map, it) }) + actual class Async(@PublishedApi internal val ios: FIRCollectionReference) { + actual inline fun add(data: T, encodeSettings: EncodeSettings) = + deferred { ios.addDocumentWithData(encode(data, encodeSettings) as Map, it) } + .convert(::DocumentReference) + + actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = + deferred { ios.addDocumentWithData(encode(strategy, data, encodeSettings) as Map, it) } + .convert(::DocumentReference) + } } actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) @@ -431,9 +447,9 @@ actual class DocumentSnapshot(val ios: FIRDocumentSnapshot) { return decode(value = data?.mapValues { (_, value) -> value?.takeIf { it !is NSNull } }) } - actual fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior): T { + actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T { val data = ios.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) - return decode(strategy, data?.mapValues { (_, value) -> value?.takeIf { it !is NSNull } }) + return decode(strategy, data?.mapValues { (_, value) -> value?.takeIf { it !is NSNull } }, decodeSettings) } actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior): T { @@ -441,9 +457,9 @@ actual class DocumentSnapshot(val ios: FIRDocumentSnapshot) { return decode(value) } - actual fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior): T { + actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T { val value = ios.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - return decode(strategy, value) + return decode(strategy, value, decodeSettings) } actual fun contains(field: String) = ios.valueForField(field) != null @@ -467,34 +483,13 @@ actual class SnapshotMetadata(val ios: FIRSnapshotMetadata) { actual class FieldPath private constructor(val ios: FIRFieldPath) { actual constructor(vararg fieldNames: String) : this(FIRFieldPath(fieldNames.asList())) actual val documentId: FieldPath get() = FieldPath(FIRFieldPath.documentID()) - + actual val encoded: EncodedFieldPath = ios override fun equals(other: Any?): Boolean = other is FieldPath && ios == other.ios override fun hashCode(): Int = ios.hashCode() override fun toString(): String = ios.toString() } -/** A class representing a platform specific Firebase FieldValue. */ -private typealias NativeFieldValue = FIRFieldValue - -/** Represents a Firebase FieldValue. */ -@Serializable(with = FieldValueSerializer::class) -actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { - init { - require(nativeValue is NativeFieldValue) - } - override fun equals(other: Any?): Boolean = - this === other || other is FieldValue && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - actual companion object { - actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.fieldValueForServerTimestamp()) - actual val delete: FieldValue get() = FieldValue(NativeFieldValue.fieldValueForDelete()) - actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.fieldValueForIntegerIncrement(value.toLong())) - actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.fieldValueForArrayUnion(elements.asList())) - actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.fieldValueForArrayRemove(elements.asList())) - } -} +actual typealias EncodedFieldPath = FIRFieldPath private fun T.throwError(block: T.(errorPointer: CPointer>) -> R): R { memScoped { @@ -532,3 +527,18 @@ suspend inline fun await(function: (callback: (NSError?) -> Unit) -> T): T { job.await() return result } + +@Suppress("DeferredIsResult") +@PublishedApi +internal inline fun deferred(function: (callback: (NSError?) -> Unit) -> T): Deferred { + val job = CompletableDeferred() + val callback = { error: NSError? -> + if(error == null) { + job.complete(Unit) + } else { + job.completeExceptionally(error.toException()) + } + } + val result = function(callback) + return job.convert { result } +} diff --git a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt new file mode 100644 index 000000000..1f70d3731 --- /dev/null +++ b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -0,0 +1,6 @@ +package dev.gitlive.firebase.firestore + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreJs +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index f28d93276..3f54909b5 100644 --- a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -8,5 +8,6 @@ actual val emulatorHost: String = "localhost" actual val context: Any = Unit -@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) -actual annotation class IgnoreForAndroidUnitTest +@Suppress("UNCHECKED_CAST") +actual fun encodedAsMap(encoded: Any?): Map = encoded as Map +actual fun Map.asEncoded(): Any = this diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt new file mode 100644 index 000000000..39aba6c99 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt @@ -0,0 +1,32 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.firestore.externals.deleteField +import kotlinx.serialization.Serializable +import dev.gitlive.firebase.firestore.externals.serverTimestamp as jsServerTimestamp +import dev.gitlive.firebase.firestore.externals.arrayRemove as jsArrayRemove +import dev.gitlive.firebase.firestore.externals.arrayUnion as jsArrayUnion +import dev.gitlive.firebase.firestore.externals.increment as jsIncrement + +/** Represents a platform specific Firebase FieldValue. */ +typealias NativeFieldValue = dev.gitlive.firebase.firestore.externals.FieldValue + +/** Represents a Firebase FieldValue. */ +@Serializable(with = FieldValueSerializer::class) +actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { + init { + require(nativeValue is NativeFieldValue) + } + override fun equals(other: Any?): Boolean = + this === other || other is FieldValue && + (nativeValue as NativeFieldValue).isEqual(other.nativeValue as NativeFieldValue) + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() + + actual companion object { + actual val serverTimestamp: FieldValue get() = rethrow { FieldValue(jsServerTimestamp()) } + actual val delete: FieldValue get() = rethrow { FieldValue(deleteField()) } + actual fun increment(value: Int): FieldValue = rethrow { FieldValue(jsIncrement(value)) } + actual fun arrayUnion(vararg elements: Any): FieldValue = rethrow { FieldValue(jsArrayUnion(*elements)) } + actual fun arrayRemove(vararg elements: Any): FieldValue = rethrow { FieldValue(jsArrayRemove(*elements)) } + } +} diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt index 9b14b7af3..2271e446e 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt @@ -1,6 +1,5 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* import kotlinx.serialization.Serializable /** A class representing a platform specific Firebase GeoPoint. */ diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt new file mode 100644 index 000000000..d12bda859 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -0,0 +1,10 @@ +package dev.gitlive.firebase.firestore + +@PublishedApi +internal actual fun isSpecialValue(value: Any) = when(value) { + is NativeFieldValue, + is NativeGeoPoint, + is NativeTimestamp, + is NativeDocumentReference -> true + else -> false +} 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 46833d684..bb93b5c13 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -4,21 +4,16 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.FirebaseException -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode +import dev.gitlive.firebase.* import dev.gitlive.firebase.firestore.externals.* -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.await +import kotlinx.coroutines.* import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.promise import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationStrategy +import kotlin.js.Json import kotlin.js.json import dev.gitlive.firebase.firestore.externals.CollectionReference as JsCollectionReference import dev.gitlive.firebase.firestore.externals.DocumentChange as JsDocumentChange @@ -30,12 +25,14 @@ 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.arrayRemove as jsArrayRemove -import dev.gitlive.firebase.firestore.externals.arrayUnion as jsArrayUnion +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.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.increment as jsIncrement 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 @@ -47,26 +44,14 @@ actual val Firebase.firestore get() = actual fun Firebase.firestore(app: FirebaseApp) = rethrow { FirebaseFirestore(getFirestore(app.js)) } -/** Helper method to perform an update operation. */ -private fun performUpdate( - fieldsAndValues: Array>, - update: (String, Any?, Array) -> R -) = performUpdate(fieldsAndValues, { it }, { encode(it, true) }, update) - -/** Helper method to perform an update operation. */ -private fun performUpdate( - fieldsAndValues: Array>, - update: (dev.gitlive.firebase.firestore.externals.FieldPath, Any?, Array) -> R -) = performUpdate(fieldsAndValues, { it.js }, { encode(it, true) }, update) - actual class FirebaseFirestore(jsFirestore: Firestore) { var js: Firestore = jsFirestore private set - actual fun collection(collectionPath: String) = rethrow { CollectionReference(collection(js, collectionPath)) } + actual fun collection(collectionPath: String) = rethrow { CollectionReference(jsCollection(js, collectionPath)) } - actual fun collectionGroup(collectionId: String) = rethrow { Query(collectionGroup(js, collectionId)) } + actual fun collectionGroup(collectionId: String) = rethrow { Query(jsCollectionGroup(js, collectionId)) } actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } @@ -76,7 +61,7 @@ actual class FirebaseFirestore(jsFirestore: Firestore) { rethrow { setLogLevel( if(loggingEnabled) "error" else "silent") } actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - rethrow { runTransaction(js, { GlobalScope.promise { Transaction(it).func() } } ).await() } + rethrow { jsRunTransaction(js, { GlobalScope.promise { Transaction(it).func() } } ).await() } actual suspend fun clearPersistence() = rethrow { clearIndexedDbPersistence(js).await() } @@ -95,56 +80,79 @@ actual class FirebaseFirestore(jsFirestore: Firestore) { } actual suspend fun disableNetwork() { - rethrow { disableNetwork(js).await() } + rethrow { jsDisableNetwork(js).await() } } actual suspend fun enableNetwork() { - rethrow { enableNetwork(js).await() } + rethrow { jsEnableNetwork(js).await() } } } -actual class WriteBatch(val js: JsWriteBatch) { - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean) = - rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("merge" to merge)) } - .let { this } - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("mergeFields" to mergeFields)) } - .let { this } - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())) } - .let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = - rethrow { js.set(documentRef.js, encode(strategy, data, encodeDefaults)!!, json("merge" to merge)) } - .let { this } +val SetOptions.js: Json get() = when (this) { + is SetOptions.Merge -> json("merge" to true) + is SetOptions.Overwrite -> json("merge" to false) + is SetOptions.MergeFields -> json("mergeFields" to fields.toTypedArray()) + is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) +} - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { js.set(documentRef.js, encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFields)) } - .let { this } +actual class WriteBatch(val js: JsWriteBatch) : BaseWriteBatch() { + + actual val async = Async(js) + + override fun setEncoded( + documentRef: DocumentReference, + encodedData: Any, + setOptions: SetOptions + ): BaseWriteBatch = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } + + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") + override fun setEncoded( + documentRef: DocumentReference, + encodedData: Any, + encodedFieldsAndValues: List>, + merge: Boolean + ): BaseWriteBatch = rethrow { + val serializedItem = encodedData as Json + val serializedFieldAndValues = json(*encodedFieldsAndValues.toTypedArray()) + + val result = serializedItem.add(serializedFieldAndValues) + if (merge) { + js.set(documentRef.js, result, json("merge" to merge)) + } else { + js.set(documentRef.js, result) + } + }.let { this } - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { js.set(documentRef.js, encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())) } - .let { this } + override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = rethrow { js.update(documentRef.js, encodedData) } + .let { this } - actual inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = - rethrow { js.update(documentRef.js, encode(data, encodeDefaults)!!) } - .let { this } + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") + override fun updateEncoded( + documentRef: DocumentReference, + encodedData: Any, + encodedFieldsAndValues: List> + ): BaseWriteBatch = rethrow { + val serializedItem = encodedData as Json + val serializedFieldAndValues = json(*encodedFieldsAndValues.toTypedArray()) - actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { js.update(documentRef.js, encode(strategy, data, encodeDefaults)!!) } - .let { this } + val result = serializedItem.add(serializedFieldAndValues) + js.update(documentRef.js, result) + }.let { this } - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = rethrow { - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> + override fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseWriteBatch = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } }.let { this } - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = rethrow { - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> + override fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseWriteBatch = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } }.let { this } @@ -153,52 +161,42 @@ actual class WriteBatch(val js: JsWriteBatch) { rethrow { js.delete(documentRef.js) } .let { this } - actual suspend fun commit() = rethrow { js.commit().await() } + actual suspend fun commit() = rethrow { async.commit().await() } + @Suppress("DeferredIsResult") + actual class Async(private val js: JsWriteBatch) { + actual fun commit() = rethrow { js.commit().asDeferred() } + } } -actual class Transaction(val js: JsTransaction) { +actual class Transaction(val js: JsTransaction) : BaseTransaction() { - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean) = - rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("merge" to merge)) } - .let { this } - - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("mergeFields" to mergeFields)) } - .let { this } - - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())) } - .let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = - rethrow { js.set(documentRef.js, encode(strategy, data, encodeDefaults)!!, json("merge" to merge)) } - .let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { js.set(documentRef.js, encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFields)) } - .let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { js.set(documentRef.js, encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())) } - .let { this } - - actual fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = - rethrow { js.update(documentRef.js, encode(data, encodeDefaults)!!) } - .let { this } + override fun setEncoded( + documentRef: DocumentReference, + encodedData: Any, + setOptions: SetOptions + ): BaseTransaction = rethrow { + js.set(documentRef.js, encodedData, setOptions.js) + } + .let { this } - actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { js.update(documentRef.js, encode(strategy, data, encodeDefaults)!!) } - .let { this } + override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = rethrow { js.update(documentRef.js, encodedData) } + .let { this } - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = rethrow { - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> + override fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseTransaction = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } }.let { this } - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = rethrow { - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> + override fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseTransaction = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } }.let { this } @@ -215,8 +213,8 @@ actual class Transaction(val js: JsTransaction) { actual typealias NativeDocumentReference = JsDocumentReference @Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) { - val js: NativeDocumentReference by ::nativeValue +actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { + val js: NativeDocumentReference = nativeValue actual val id: String get() = rethrow { js.id } @@ -227,47 +225,11 @@ actual class DocumentReference actual constructor(internal actual val nativeValu actual val parent: CollectionReference get() = rethrow { CollectionReference(js.parent) } - actual fun collection(collectionPath: String) = rethrow { CollectionReference(collection(js, collectionPath)) } + override val async = Async(nativeValue) - actual suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean) = - rethrow { setDoc(js, encode(data, encodeDefaults)!!, json("merge" to merge)).await() } + actual fun collection(collectionPath: String) = rethrow { CollectionReference(jsCollection(js, collectionPath)) } - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { setDoc(js, encode(data, encodeDefaults)!!, json("mergeFields" to mergeFields)).await() } - - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { setDoc(js, encode(data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())).await() } - - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = - rethrow { setDoc(js, encode(strategy, data, encodeDefaults)!!, json("merge" to merge)).await() } - - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { setDoc(js, encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFields)).await() } - - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { setDoc(js, encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())).await() } - - actual suspend inline fun update(data: T, encodeDefaults: Boolean) = - rethrow { jsUpdate(js, encode(data, encodeDefaults)!!).await() } - - actual suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { jsUpdate(js, encode(strategy, data, encodeDefaults)!!).await() } - - actual suspend fun update(vararg fieldsAndValues: Pair) = rethrow { - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - jsUpdate(js, field, value, *moreFieldsAndValues) - }?.await() - }.run { Unit } - - actual suspend fun update(vararg fieldsAndValues: Pair) = rethrow { - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - jsUpdate(js, field, value, *moreFieldsAndValues) - }?.await() - }.run { Unit } - - actual suspend fun delete() = rethrow { deleteDoc(js).await() } - - actual suspend fun get() = rethrow { DocumentSnapshot(getDoc(js).await()) } + actual suspend fun get() = rethrow { DocumentSnapshot( getDoc(js).await()) } actual val snapshots: Flow get() = snapshots() @@ -285,6 +247,34 @@ actual class DocumentReference actual constructor(internal actual val nativeValu this === other || other is DocumentReference && refEqual(nativeValue, other.nativeValue) override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = "DocumentReference(path=$path)" + + @Suppress("DeferredIsResult") + class Async(@PublishedApi internal val js: NativeDocumentReference) : BaseDocumentReference.Async() { + + override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = rethrow { + setDoc(js, encodedData, setOptions.js).asDeferred() + } + + override fun updateEncoded(encodedData: Any): Deferred = rethrow { jsUpdate(js, encodedData).asDeferred() } + + override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = rethrow { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + jsUpdate(js, field, value, *moreFieldsAndValues) + } + ?.asDeferred() ?: CompletableDeferred(Unit) + } + + override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = rethrow { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + jsUpdate(js, field, value, *moreFieldsAndValues) + } + ?.asDeferred() ?: CompletableDeferred(Unit) + } + + override fun delete() = rethrow { deleteDoc(js).asDeferred() } + } } actual open class Query(open val js: JsQuery) { @@ -383,6 +373,7 @@ actual class CollectionReference(override val js: JsCollectionReference) : Query actual val path: String get() = rethrow { js.path } + actual val async = Async(js) actual val document get() = rethrow { DocumentReference(doc(js)) } @@ -390,13 +381,24 @@ actual class CollectionReference(override val js: JsCollectionReference) : Query actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } - actual suspend inline fun add(data: T, encodeDefaults: Boolean) = - rethrow { DocumentReference(addDoc(js, encode(data, encodeDefaults)!!).await()) } - - actual suspend fun add(data: T, strategy: SerializationStrategy, encodeDefaults: Boolean) = - rethrow { DocumentReference(addDoc(js, encode(strategy, data, encodeDefaults)!!).await()) } - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { DocumentReference(addDoc(js, encode(strategy, data, encodeDefaults)!!).await()) } + actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = + rethrow { DocumentReference(addDoc(js, encode(data, encodeSettings)!!).await()) } + actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = + rethrow { DocumentReference(addDoc(js, encode(strategy, data, encodeSettings)!!).await()) } + + @Suppress("DeferredIsResult") + actual class Async(@PublishedApi internal val js: JsCollectionReference) { + actual inline fun add(data: T, encodeSettings: EncodeSettings) = + rethrow { + addDoc(js, encode(data, encodeSettings)!!).asDeferred() + .convert(::DocumentReference) + } + actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = + rethrow { + addDoc(js, encode(strategy, data, encodeSettings)!!).asDeferred() + .convert(::DocumentReference) + } + } } actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) @@ -431,14 +433,14 @@ actual class DocumentSnapshot(val js: JsDocumentSnapshot) { actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T = rethrow { decode(value = js.data(getTimestampsOptions(serverTimestampBehavior))) } - actual fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior): T = - rethrow { decode(strategy, js.data(getTimestampsOptions(serverTimestampBehavior))) } + actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = + rethrow { decode(strategy, js.data(getTimestampsOptions(serverTimestampBehavior)), decodeSettings) } actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior) = rethrow { decode(value = js.get(field, getTimestampsOptions(serverTimestampBehavior))) } - actual fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior) = - rethrow { decode(strategy, js.get(field, getTimestampsOptions(serverTimestampBehavior))) } + actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior) = + rethrow { decode(strategy, js.get(field, getTimestampsOptions(serverTimestampBehavior)), decodeSettings) } actual fun contains(field: String) = rethrow { js.get(field) != undefined } actual val exists get() = rethrow { js.exists() } @@ -458,35 +460,13 @@ actual class FieldPath private constructor(val js: JsFieldPath) { js("Reflect").construct(JsFieldPath, fieldNames).unsafeCast() }) actual val documentId: FieldPath get() = FieldPath(JsFieldPath.documentId) - + actual val encoded: EncodedFieldPath = js override fun equals(other: Any?): Boolean = other is FieldPath && js.isEqual(other.js) override fun hashCode(): Int = js.hashCode() override fun toString(): String = js.toString() } -/** Represents a platform specific Firebase FieldValue. */ -private typealias NativeFieldValue = dev.gitlive.firebase.firestore.externals.FieldValue - -/** Represents a Firebase FieldValue. */ -@Serializable(with = FieldValueSerializer::class) -actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { - init { - require(nativeValue is NativeFieldValue) - } - override fun equals(other: Any?): Boolean = - this === other || other is FieldValue && - (nativeValue as NativeFieldValue).isEqual(other.nativeValue as NativeFieldValue) - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - actual companion object { - actual val serverTimestamp: FieldValue get() = rethrow { FieldValue(serverTimestamp()) } - actual val delete: FieldValue get() = rethrow { FieldValue(deleteField()) } - actual fun increment(value: Int): FieldValue = rethrow { FieldValue(jsIncrement(value)) } - actual fun arrayUnion(vararg elements: Any): FieldValue = rethrow { FieldValue(jsArrayUnion(*elements)) } - actual fun arrayRemove(vararg elements: Any): FieldValue = rethrow { FieldValue(jsArrayRemove(*elements)) } - } -} +actual typealias EncodedFieldPath = JsFieldPath //actual data class FirebaseFirestoreSettings internal constructor( // val cacheSizeBytes: Number? = undefined, diff --git a/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt new file mode 100644 index 000000000..65c18a3c9 --- /dev/null +++ b/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -0,0 +1,7 @@ +package dev.gitlive.firebase.firestore + +import kotlin.test.Ignore + +actual typealias IgnoreJs = Ignore +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 52e53d749..7dc9a19a8 100644 --- a/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -4,10 +4,16 @@ package dev.gitlive.firebase.firestore +import kotlin.js.json + actual val emulatorHost: String = "localhost" actual val context: Any = Unit -@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) -actual annotation class IgnoreForAndroidUnitTest - +actual fun encodedAsMap(encoded: Any?): Map { + return (js("Object").entries(encoded) as Array>).associate { + it[0] as String to it[1] + } +} +actual fun Map.asEncoded(): Any = + json(*entries.map { (key, value) -> key to value }.toTypedArray()) diff --git a/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt new file mode 100644 index 000000000..1f70d3731 --- /dev/null +++ b/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -0,0 +1,6 @@ +package dev.gitlive.firebase.firestore + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreJs +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 6eb9f93a2..e56e244c6 100644 --- a/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -9,5 +9,5 @@ actual val emulatorHost: String = "10.0.2.2" actual val context: Any = Unit -@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) -actual annotation class IgnoreForAndroidUnitTest +actual fun encodedAsMap(encoded: Any?): Map = encoded as Map +actual fun Map.asEncoded(): Any = this 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 10ddc4821..7fe9a81bc 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 @@ -4,6 +4,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 @@ -25,21 +26,17 @@ actual fun Firebase.functions(app: FirebaseApp) = actual fun Firebase.functions(app: FirebaseApp, region: String) = FirebaseFunctions(com.google.firebase.functions.FirebaseFunctions.getInstance(app.android, region)) -actual class FirebaseFunctions internal constructor(val android: com.google.firebase.functions.FirebaseFunctions) { +actual data class FirebaseFunctions internal constructor(val android: com.google.firebase.functions.FirebaseFunctions) { actual fun httpsCallable(name: String, timeout: Long?) = HttpsCallableReference(android.getHttpsCallable(name).apply { timeout?.let { setTimeout(it, TimeUnit.MILLISECONDS) } }) actual fun useEmulator(host: String, port: Int) = android.useEmulator(host, port) } -actual class HttpsCallableReference internal constructor(val android: com.google.firebase.functions.HttpsCallableReference) { +actual class HttpsCallableReference internal constructor(val android: com.google.firebase.functions.HttpsCallableReference) : BaseHttpsCallableReference() { actual suspend operator fun invoke() = HttpsCallableResult(android.call().await()) - actual suspend operator inline fun invoke(data: T, encodeDefaults: Boolean) = - HttpsCallableResult(android.call(encode(data, encodeDefaults)).await()) - - actual suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - HttpsCallableResult(android.call(encode(strategy, data, encodeDefaults)).await()) + override suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(android.call(encodedData).await()) } actual class HttpsCallableResult constructor(val android: com.google.firebase.functions.HttpsCallableResult) { @@ -47,8 +44,8 @@ actual class HttpsCallableResult constructor(val android: com.google.firebase.fu actual inline fun data() = decode(value = android.data) - actual fun data(strategy: DeserializationStrategy) = - decode(strategy, android.data) + actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = + decode(strategy, android.data, decodeSettings) } actual typealias FirebaseFunctionsException = com.google.firebase.functions.FirebaseFunctionsException @@ -58,4 +55,3 @@ actual val FirebaseFunctionsException.code: FunctionsExceptionCode get() = code actual val FirebaseFunctionsException.details: Any? get() = details actual typealias FunctionsExceptionCode = com.google.firebase.functions.FirebaseFunctionsException.Code - 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 300c66726..531846b44 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,9 +4,7 @@ package dev.gitlive.firebase.functions -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.* import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy @@ -15,15 +13,19 @@ expect class FirebaseFunctions { fun useEmulator(host: String, port: Int) } -expect class HttpsCallableReference { - suspend operator inline fun invoke(data: T, encodeDefaults: Boolean = true): HttpsCallableResult - suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true): HttpsCallableResult +abstract class BaseHttpsCallableReference { + suspend inline operator fun invoke(data: T, encodeSettings: EncodeSettings = EncodeSettings()): HttpsCallableResult = invoke(encode(data, encodeSettings)!!) + suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): HttpsCallableResult = invoke(encode(strategy, data, encodeSettings)!!) + abstract suspend fun invoke(encodedData: Any): HttpsCallableResult +} + +expect class HttpsCallableReference : BaseHttpsCallableReference { suspend operator fun invoke(): HttpsCallableResult } expect class HttpsCallableResult { inline fun data(): T - fun data(strategy: DeserializationStrategy): T + fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings()): T } /** Returns the [FirebaseFunctions] instance of the default [FirebaseApp]. */ 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 8ff0a5e11..a3616ff21 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 @@ -19,10 +19,12 @@ actual val Firebase.functions actual fun Firebase.functions(region: String) = FirebaseFunctions(FIRFunctions.functionsForRegion(region)) +@Suppress("CAST_NEVER_SUCCEEDS") actual fun Firebase.functions(app: FirebaseApp): FirebaseFunctions = FirebaseFunctions( FIRFunctions.functionsForApp(app.ios as objcnames.classes.FIRApp) ) +@Suppress("CAST_NEVER_SUCCEEDS") actual fun Firebase.functions( app: FirebaseApp, region: String, @@ -30,21 +32,17 @@ actual fun Firebase.functions( FIRFunctions.functionsForApp(app.ios as objcnames.classes.FIRApp, region = region) ) -actual class FirebaseFunctions internal constructor(val ios: FIRFunctions) { +actual data class FirebaseFunctions internal constructor(val ios: FIRFunctions) { actual fun httpsCallable(name: String, timeout: Long?) = HttpsCallableReference(ios.HTTPSCallableWithName(name).apply { timeout?.let { setTimeoutInterval(it/1000.0) } }) actual fun useEmulator(host: String, port: Int) = ios.useEmulatorWithHost(host, port.toLong()) } -actual class HttpsCallableReference internal constructor(val ios: FIRHTTPSCallable) { +actual class HttpsCallableReference internal constructor(val ios: FIRHTTPSCallable) : BaseHttpsCallableReference() { actual suspend operator fun invoke() = HttpsCallableResult(ios.awaitResult { callWithCompletion(it) }) - actual suspend inline operator fun invoke(data: T, encodeDefaults: Boolean) = - HttpsCallableResult(ios.awaitResult { callWithObject(encode(data, encodeDefaults), it) }) - - actual suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - HttpsCallableResult(ios.awaitResult { callWithObject(encode(strategy, data, encodeDefaults), it) }) + override suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(ios.awaitResult { callWithObject(encodedData, it) }) } actual class HttpsCallableResult constructor(val ios: FIRHTTPSCallableResult) { @@ -52,8 +50,8 @@ actual class HttpsCallableResult constructor(val ios: FIRHTTPSCallableResult) { actual inline fun data() = decode(value = ios.data()) - actual fun data(strategy: DeserializationStrategy) = - decode(strategy, ios.data()) + actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = + decode(strategy, ios.data(), decodeSettings) } actual class FirebaseFunctionsException(message: String, val code: FunctionsExceptionCode, val details: Any?) : FirebaseException(message) 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 1e06a16c5..4db0c0284 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 @@ -32,16 +32,14 @@ actual class FirebaseFunctions internal constructor(val js: Functions) { } @Suppress("UNCHECKED_CAST") -actual class HttpsCallableReference internal constructor(val js: HttpsCallable) { +actual class HttpsCallableReference internal constructor(val js: HttpsCallable) : BaseHttpsCallableReference() { actual suspend operator fun invoke() = rethrow { HttpsCallableResult(js().await()) } - actual suspend inline operator fun invoke(data: T, encodeDefaults: Boolean) = - rethrow { HttpsCallableResult(js(encode(data, encodeDefaults)).await()) } - - actual suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { HttpsCallableResult(js(encode(strategy, data, encodeDefaults)).await()) } + override suspend fun invoke(encodedData: Any): HttpsCallableResult = rethrow { + HttpsCallableResult(js(encodedData).await()) + } } actual class HttpsCallableResult constructor(val js: JsHttpsCallableResult) { @@ -49,8 +47,8 @@ actual class HttpsCallableResult constructor(val js: JsHttpsCallableResult) { actual inline fun data() = rethrow { decode(value = js.data) } - actual fun data(strategy: DeserializationStrategy) = - rethrow { decode(strategy, js.data) } + actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = + rethrow { decode(strategy, js.data, decodeSettings) } } From 69e4516db3b353de99dbb89659410d126fe27e26 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 3 Jan 2024 15:38:28 +0100 Subject: [PATCH 02/27] Ensure API stability --- .../dev/gitlive/firebase/database/database.kt | 43 +++++---- .../dev/gitlive/firebase/database/database.kt | 44 +++++++-- .../dev/gitlive/firebase/database/database.kt | 36 +++---- .../dev/gitlive/firebase/database/database.kt | 40 ++++---- .../gitlive/firebase/firestore/firestore.kt | 70 +++----------- .../gitlive/firebase/firestore/firestore.kt | 95 +++++++++++++------ .../gitlive/firebase/firestore/firestore.kt | 18 +--- .../gitlive/firebase/firestore/firestore.kt | 81 ++++------------ .../gitlive/firebase/firestore/firestore.kt | 79 ++++----------- .../gitlive/firebase/functions/functions.kt | 3 + 10 files changed, 219 insertions(+), 290 deletions(-) diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index ce075b2e6..ddd18f708 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 @@ -16,7 +16,6 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationStrategy import java.util.* import kotlin.time.Duration.Companion.seconds @@ -72,10 +71,23 @@ actual class FirebaseDatabase private constructor(val android: com.google.fireba android.useEmulator(host, port) } -actual open class Query internal constructor( - open val android: com.google.firebase.database.Query, - val persistenceEnabled: Boolean +actual data class NativeQuery( + val android: com.google.firebase.database.Query, + val persistenceEnabled: Boolean, +) + +actual open class Query internal actual constructor( + nativeQuery: NativeQuery ) { + + internal constructor( + android: com.google.firebase.database.Query, + persistenceEnabled: Boolean + ) : this(NativeQuery(android, persistenceEnabled)) + + open val android: com.google.firebase.database.Query = nativeQuery.android + val persistenceEnabled: Boolean = nativeQuery.persistenceEnabled + actual fun orderByKey() = Query(android.orderByKey(), persistenceEnabled) actual fun orderByValue() = Query(android.orderByValue(), persistenceEnabled) @@ -156,7 +168,7 @@ actual open class Query internal constructor( actual class DatabaseReference internal constructor( override val android: com.google.firebase.database.DatabaseReference, persistenceEnabled: Boolean -): Query(android, persistenceEnabled) { +): BaseDatabaseReference(NativeQuery(android, persistenceEnabled)) { actual val key get() = android.key val database = FirebaseDatabase(android.database) @@ -166,17 +178,12 @@ actual class DatabaseReference internal constructor( actual fun push() = DatabaseReference(android.push(), persistenceEnabled) actual fun onDisconnect() = OnDisconnect(android.onDisconnect(), persistenceEnabled, database) - actual suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings) = android.setValue(encode(value, encodeSettings)) + override suspend fun setValueEncoded(encodedValue: Any?) = android.setValue(encodedValue) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = - android.setValue(encode(strategy, value, encodeSettings)) - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = + override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = android.updateChildren(encode(update, encodeSettings) as Map) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @@ -241,7 +248,7 @@ actual class OnDisconnect internal constructor( val android: com.google.firebase.database.OnDisconnect, val persistenceEnabled: Boolean, val database: FirebaseDatabase, -) { +) : BaseOnDisconnect() { actual suspend fun removeValue() = android.removeValue() .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } @@ -251,17 +258,11 @@ actual class OnDisconnect internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend inline fun setValue(value: T, encodeSettings: EncodeSettings) = - android.setValue(encode(value, encodeSettings)) + override suspend fun setValue(encodedValue: Any?) = android.setValue(encodedValue) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = - android.setValue(encode(strategy, value, encodeSettings)) - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit} - - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = + override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = android.updateChildren(update.mapValues { (_, it) -> encode(it, encodeSettings) }) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index e2fdf22a8..97a6caccc 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 @@ -9,6 +9,7 @@ import dev.gitlive.firebase.EncodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type.* +import dev.gitlive.firebase.encode import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer @@ -47,7 +48,9 @@ data class ChildEvent internal constructor( } } -expect open class Query { +expect class NativeQuery + +expect open class Query internal constructor(nativeQuery: NativeQuery) { val valueEvents: Flow fun childEvents(vararg types: ChildEvent.Type = arrayOf(ADDED, CHANGED, MOVED, REMOVED)): Flow fun orderByKey(): Query @@ -66,14 +69,26 @@ expect open class Query { fun equalTo(value: Boolean, key: String? = null): Query } -expect class DatabaseReference : Query { +abstract class BaseDatabaseReference internal constructor(nativeQuery: NativeQuery) : Query(nativeQuery) { + suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = + setValue(value, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) + suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings = EncodeSettings()) = + setValueEncoded(encode(value, encodeSettings)) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = + setValue(strategy, value, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) = setValueEncoded(encode(strategy, value, encodeSettings)) + + abstract suspend fun setValueEncoded(encodedValue: Any?) + suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) + abstract suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) +} + +expect class DatabaseReference : BaseDatabaseReference { val key: String? fun push(): DatabaseReference fun child(path: String): DatabaseReference fun onDisconnect(): OnDisconnect - suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings = EncodeSettings()) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) - suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) + suspend fun removeValue() suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings = DecodeSettings(), transactionUpdate: (currentData: T) -> T): DataSnapshot @@ -93,10 +108,21 @@ expect class DataSnapshot { expect class DatabaseException(message: String?, cause: Throwable?) : RuntimeException -expect class OnDisconnect { +abstract class BaseOnDisconnect internal constructor() { + suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = + setValue(value, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) + suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings = EncodeSettings()) = + setValue(encode(value, encodeSettings)) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = + setValue(strategy, value, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) = setValue(encode(strategy, value, encodeSettings)) + abstract suspend fun setValue(encodedValue: Any?) + + abstract suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) + suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) +} + +expect class OnDisconnect : BaseOnDisconnect { suspend fun removeValue() suspend fun cancel() - suspend inline fun setValue(value: T, encodeSettings: EncodeSettings = EncodeSettings()) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) - suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) } 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 e1f0198f7..d64e6b04c 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 @@ -65,10 +65,20 @@ fun Type.toEventType() = when(this) { REMOVED -> FIRDataEventTypeChildRemoved } -actual open class Query internal constructor( +actual data class NativeQuery( open val ios: FIRDatabaseQuery, val persistenceEnabled: Boolean +) + +actual open class Query internal actual constructor( + nativeQuery: NativeQuery ) { + + internal constructor(ios: FIRDatabaseQuery, persistenceEnabled: Boolean) : this(NativeQuery(ios, persistenceEnabled)) + + open val ios: FIRDatabaseQuery = nativeQuery.ios + val persistenceEnabled: Boolean = nativeQuery.persistenceEnabled + actual fun orderByKey() = Query(ios.queryOrderedByKey(), persistenceEnabled) actual fun orderByValue() = Query(ios.queryOrderedByValue(), persistenceEnabled) @@ -127,7 +137,7 @@ actual open class Query internal constructor( actual class DatabaseReference internal constructor( override val ios: FIRDatabaseReference, persistenceEnabled: Boolean -): Query(ios, persistenceEnabled) { +): BaseDatabaseReference(NativeQuery(ios, persistenceEnabled)) { actual val key get() = ios.key @@ -136,16 +146,12 @@ actual class DatabaseReference internal constructor( actual fun push() = DatabaseReference(ios.childByAutoId(), persistenceEnabled) actual fun onDisconnect() = OnDisconnect(ios, persistenceEnabled) - actual suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { setValue(encode(value, encodeSettings), it) } - } - - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { setValue(encode(strategy, value, encodeSettings), it) } + override suspend fun setValueEncoded(encodedValue: Any?) { + ios.await(persistenceEnabled) { setValue(encodedValue, it) } } @Suppress("UNCHECKED_CAST") - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { + override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { ios.await(persistenceEnabled) { updateChildValues(encode(update, encodeSettings) as Map, it) } } @@ -203,7 +209,7 @@ actual class DataSnapshot internal constructor( actual class OnDisconnect internal constructor( val ios: FIRDatabaseReference, val persistenceEnabled: Boolean -) { +) : BaseOnDisconnect() { actual suspend fun removeValue() { ios.await(persistenceEnabled) { onDisconnectRemoveValueWithCompletionBlock(it) } } @@ -212,16 +218,12 @@ actual class OnDisconnect internal constructor( ios.await(persistenceEnabled) { cancelDisconnectOperationsWithCompletionBlock(it) } } - actual suspend inline fun setValue(value: T, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { onDisconnectSetValue(encode(value, encodeSettings), it) } - } - - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { onDisconnectSetValue(encode(strategy, value, encodeSettings), it) } + override suspend fun setValue(encodedValue: Any?) { + ios.await(persistenceEnabled) { onDisconnectSetValue(encodedValue, it) } } @Suppress("UNCHECKED_CAST") - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { + override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it, encodeSettings) } as Map, it) } } } diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index 9d12693e5..e1456e9ed 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 @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.produceIn import kotlinx.coroutines.selects.select import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationStrategy import kotlin.js.Promise import kotlin.js.json import dev.gitlive.firebase.database.externals.DataSnapshot as JsDataSnapshot @@ -52,11 +51,20 @@ actual class FirebaseDatabase internal constructor(val js: Database) { actual fun useEmulator(host: String, port: Int) = rethrow { connectDatabaseEmulator(js, host, port) } } -actual open class Query internal constructor( - open val js: JsQuery, +actual data class NativeQuery( + val js: JsQuery, val database: Database +) + +actual open class Query internal actual constructor( + nativeQuery: NativeQuery ) { + internal constructor(js: JsQuery, database: Database) : this(NativeQuery(js, database)) + + open val js: JsQuery = nativeQuery.js + val database: Database = nativeQuery.database + actual fun orderByKey() = Query(query(js, jsOrderByKey()), database) actual fun orderByValue() = Query(query(js, jsOrderByValue()), database) actual fun orderByChild(path: String) = Query(query(js, jsOrderByChild(path)), database) @@ -130,7 +138,7 @@ actual open class Query internal constructor( actual class DatabaseReference internal constructor( override val js: JsDatabaseReference, database: Database -) : Query(js, database) { +) : BaseDatabaseReference(NativeQuery(js, database)) { actual val key get() = rethrow { js.key } actual fun push() = rethrow { DatabaseReference(push(js), database) } @@ -138,17 +146,15 @@ actual class DatabaseReference internal constructor( actual fun onDisconnect() = rethrow { OnDisconnect(onDisconnect(js), database) } - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = - rethrow { update(js, encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } - actual suspend fun removeValue() = rethrow { remove(js).awaitWhileOnline(database) } - actual suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings) = rethrow { - set(js, encode(value, encodeSettings)).awaitWhileOnline(database) + override suspend fun setValueEncoded(encodedValue: Any?) = rethrow { + set(js, encodedValue).awaitWhileOnline(database) } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = - rethrow { set(js, encode(strategy, value, encodeSettings)).awaitWhileOnline(database) } + override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = + rethrow { update(js, encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } + actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { return DataSnapshot(jsRunTransaction(js, transactionUpdate).awaitWhileOnline(database).snapshot, database) @@ -187,19 +193,17 @@ actual class DataSnapshot internal constructor( actual class OnDisconnect internal constructor( val js: JsOnDisconnect, val database: Database -) { +) : BaseOnDisconnect() { actual suspend fun removeValue() = rethrow { js.remove().awaitWhileOnline(database) } actual suspend fun cancel() = rethrow { js.cancel().awaitWhileOnline(database) } - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = - rethrow { js.update(encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } + override suspend fun setValue(encodedValue: Any?) = + rethrow { js.set(encodedValue).awaitWhileOnline(database) } - actual suspend inline fun setValue(value: T, encodeSettings: EncodeSettings) = - rethrow { js.set(encode(value, encodeSettings)).awaitWhileOnline(database) } + override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = + rethrow { js.update(encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = - rethrow { js.set(encode(strategy, value, encodeSettings)).awaitWhileOnline(database) } } actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) { 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 aa76abbd3..c9ef85468 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 @@ -99,41 +99,9 @@ actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) : this } - @Suppress("UNCHECKED_CAST") - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List>, - merge: Boolean - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() - - val result = serializedItem + serializedFieldAndValues - if (merge) { - android.set(documentRef.android, result, com.google.firebase.firestore.SetOptions.merge()) - } else { - android.set(documentRef.android, result) - } - return this - } - @Suppress("UNCHECKED_CAST") override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = android.update(documentRef.android, encodedData as Map).let { this } - @Suppress("UNCHECKED_CAST") - override fun updateEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List> - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() - - val result = serializedItem + serializedFieldAndValues - return android.update(documentRef.android, result).let { this } - } - override fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> @@ -258,7 +226,11 @@ actual class DocumentReference actual constructor(internal actual val nativeValu } } -actual open class Query(open val android: com.google.firebase.firestore.Query) { +actual typealias NativeQuery = com.google.firebase.firestore.Query + +actual open class Query internal actual constructor(nativeQuery: NativeQuery) { + + open val android = nativeQuery actual suspend fun get() = QuerySnapshot(android.get().await()) @@ -332,11 +304,11 @@ actual open class Query(open val android: com.google.firebase.firestore.Query) { actual typealias Direction = com.google.firebase.firestore.Query.Direction actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type -actual class CollectionReference(override val android: com.google.firebase.firestore.CollectionReference) : Query(android) { +actual class CollectionReference(override val android: com.google.firebase.firestore.CollectionReference) : BaseCollectionReference(android) { actual val path: String get() = android.path - actual val async = Async(android) + override val async = Async(android) actual val document: DocumentReference get() = DocumentReference(android.document()) @@ -346,17 +318,9 @@ actual class CollectionReference(override val android: com.google.firebase.fires actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) - actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = - DocumentReference(android.add(encode(data, encodeSettings)!!).await()) - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - DocumentReference(android.add(encode(strategy, data, encodeSettings)!!).await()) - @Suppress("DeferredIsResult") - actual class Async(@PublishedApi internal val android: com.google.firebase.firestore.CollectionReference) { - actual inline fun add(data: T, encodeSettings: EncodeSettings) = - android.add(encode(data, encodeSettings)!!).asDeferred().convert(::DocumentReference) - actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - android.add(encode(strategy, data, encodeSettings)!!).asDeferred().convert(::DocumentReference) + class Async(@PublishedApi internal val android: com.google.firebase.firestore.CollectionReference) : BaseCollectionReference.Async() { + override fun addEncoded(data: Any): Deferred = android.add(data).asDeferred().convert(::DocumentReference) } } @@ -385,23 +349,13 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC get() = android.type } -@Suppress("UNCHECKED_CAST") -actual class DocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) { +actual class DocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) : BaseDocumentSnapshot() { actual val id get() = android.id actual val reference get() = DocumentReference(android.reference) - actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T = - decode(value = android.getData(serverTimestampBehavior.toAndroid())) - - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.getData(serverTimestampBehavior.toAndroid()), decodeSettings) - - actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(value = android.get(field, serverTimestampBehavior.toAndroid())) - - actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.get(field, serverTimestampBehavior.toAndroid()), decodeSettings) + override fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = android.get(field, serverTimestampBehavior.toAndroid()) + override fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = android.getData(serverTimestampBehavior.toAndroid()) actual fun contains(field: String) = android.contains(field) 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 f5c62358d..5d045a584 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 @@ -8,9 +8,7 @@ import dev.gitlive.firebase.* import kotlinx.coroutines.Deferred import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationStrategy import kotlin.jvm.JvmName @@ -35,8 +33,8 @@ expect class FirebaseFirestore { } sealed class SetOptions { - object Merge : SetOptions() - object Overwrite : 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 } @@ -45,17 +43,25 @@ sealed class SetOptions { abstract class BaseTransaction { + fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean = false): BaseTransaction = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) protected abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseTransaction + fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = update(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) fun update(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(data, encodeSettings)!!) + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!) @JvmName("updateFields") @@ -73,7 +79,9 @@ expect class Transaction : BaseTransaction { suspend fun get(documentRef: DocumentReference): DocumentSnapshot } -expect open class Query { +expect class NativeQuery + +expect open class Query internal constructor(nativeQuery: NativeQuery) { fun limit(limit: Number): Query val snapshots: Flow fun snapshots(includeMetadataChanges: Boolean = false): Flow @@ -132,31 +140,35 @@ fun Query.endAt(document: DocumentSnapshot) = _endAt(document) fun Query.endAt(vararg fieldValues: Any) = _endAt(*(fieldValues.map { it.value }.toTypedArray())) abstract class BaseWriteBatch { + + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = setEncoded(documentRef, encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String)= + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false, vararg fieldsAndValues: Pair) = - setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty(), merge) abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseWriteBatch - abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, encodedFieldsAndValues: List>, merge: Boolean): BaseWriteBatch + inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = update(documentRef, data, EncodeSettings(encodeDefaults)) inline fun update(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(data, encodeSettings)!!) + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!) - inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg fieldsAndValues: Pair) = - updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) @JvmName("updateField") fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) @@ -164,7 +176,6 @@ abstract class BaseWriteBatch { fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch - abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any, encodedFieldsAndValues: List>): BaseWriteBatch protected abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch protected abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch @@ -218,29 +229,35 @@ abstract class BaseDocumentReference { abstract val async: Async + suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = async.set(data, encodeSettings, merge).await() + suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = async.set(data, encodeSettings, mergeFields = mergeFields).await() + suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = async.set(data, encodeSettings, mergeFieldPaths = mergeFieldPaths).await() + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = async.set(strategy, data, encodeSettings, merge).await() + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = async.set(strategy, data, encodeSettings, mergeFields = mergeFields).await() + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = async.set(strategy, data, encodeSettings, mergeFieldPaths = mergeFieldPaths).await() - @Suppress("UNCHECKED_CAST") + suspend inline fun update(data: T, encodeDefaults: Boolean) = update(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) suspend inline fun update(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = async.update(data, encodeSettings).await() - @Suppress("UNCHECKED_CAST") + suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) suspend fun update(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = async.update(strategy, data, encodeSettings).await() @@ -271,20 +288,34 @@ expect class DocumentReference internal constructor(nativeValue: NativeDocumentR suspend fun get(): DocumentSnapshot } -expect class CollectionReference : Query { +abstract class BaseCollectionReference(nativeQuery: NativeQuery) : Query(nativeQuery) { + + @Suppress("DeferredIsResult") + abstract class Async { + inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = addEncoded( + encode(data, encodeSettings)!! + ) + fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = addEncoded( + encode(strategy, data, encodeSettings)!! + ) + + abstract fun addEncoded(data: Any): Deferred + } + + suspend inline fun add(data: T, encodeDefaults: Boolean) = add(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) + suspend inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = async.add(data, encodeSettings).await() + suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = add(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) + suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = async.add(strategy, data, encodeSettings).await() + + abstract val async: Async +} + +expect class CollectionReference : BaseCollectionReference { val path: String - val async: Async val document: DocumentReference val parent: DocumentReference? fun document(documentPath: String): DocumentReference - suspend inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()): DocumentReference - suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): DocumentReference - @Suppress("DeferredIsResult") - class Async { - inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()): Deferred - fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): Deferred - } } expect class FirebaseFirestoreException : FirebaseException @@ -336,15 +367,21 @@ expect class DocumentChange { val type: ChangeType } -expect class DocumentSnapshot { +abstract class BaseDocumentSnapshot { + inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, decodeSettings: DecodeSettings = DecodeSettings()): T = decode(value = getEncoded(field, serverTimestampBehavior), decodeSettings) + fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, decodeSettings: DecodeSettings = DecodeSettings()): T = decode(strategy, getEncoded(field, serverTimestampBehavior), decodeSettings) - inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T - fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings(), serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T + abstract fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - fun contains(field: String): Boolean + inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, decodeSettings: DecodeSettings = DecodeSettings()): T = decode(encodedData(serverTimestampBehavior), decodeSettings) + fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, decodeSettings: DecodeSettings = DecodeSettings()): T = decode(strategy, encodedData(serverTimestampBehavior), decodeSettings) + + abstract fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? +} - inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T - fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings(), serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T +expect class DocumentSnapshot : BaseDocumentSnapshot { + + fun contains(field: String): Boolean val exists: Boolean val id: String 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 08d10b89e..502459d1d 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 @@ -195,7 +195,7 @@ class FirebaseFirestoreTest { } @Test - fun testExtendedSetBatch() = runTest { + fun testSetBatch() = runTest { val doc = firestore .collection("testServerTestSetBatch") .document("test") @@ -207,13 +207,9 @@ class FirebaseFirestoreTest { prop1 = "prop1", time = 123.0 ), - fieldsAndValues = arrayOf( - "time" to 124.0 - ) ) batch.commit() - assertEquals(124.0, doc.get().get("time")) assertEquals("prop1", doc.get().data(FirestoreTest.serializer()).prop1) } @@ -485,8 +481,7 @@ class FirebaseFirestoreTest { data = FirestoreTest( prop1 = "prop1-set", time = 125.0 - ), - fieldsAndValues = arrayOf>() + ) ) batch.commit() @@ -495,7 +490,7 @@ class FirebaseFirestoreTest { } @Test - fun testExtendedUpdateBatch() = runTest { + fun testUpdateBatch() = runTest { val doc = firestore .collection("testServerTestSetBatch") .document("test").apply { @@ -517,13 +512,9 @@ class FirebaseFirestoreTest { time = 123.0 ), encodeSettings = EncodeSettings(shouldEncodeElementDefault = false), - fieldsAndValues = arrayOf( - "time" to FieldValue.delete - ) ) batch.commit() - assertEquals(null, doc.get().get("time") as Double?) assertEquals("prop1-updated", doc.get().data(FirestoreTest.serializer()).prop1) } @@ -547,8 +538,7 @@ class FirebaseFirestoreTest { prop1 = "prop1-set", time = 126.0 ), - encodeSettings = EncodeSettings(shouldEncodeElementDefault = false), - fieldsAndValues = arrayOf>() + encodeSettings = EncodeSettings(shouldEncodeElementDefault = false) ) batch.commit() 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 a40eca557..e842464c8 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 @@ -88,34 +88,8 @@ actual class WriteBatch(val ios: FIRWriteBatch) : BaseWriteBatch() { is SetOptions.MergeFieldPaths -> ios.setData(encodedData as Map, documentRef.ios, setOptions.encodedFieldPaths) }.let { this } - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List>, - merge: Boolean - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() - - val result = serializedItem + serializedFieldAndValues - ios.setData(result as Map, documentRef.ios, merge) - return this - } - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = ios.updateData(encodedData as Map, documentRef.ios).let { this } - override fun updateEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List> - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() - - val result = serializedItem + serializedFieldAndValues - return ios.updateData(result as Map, documentRef.ios).let { this } - } - override fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> @@ -256,7 +230,11 @@ actual class DocumentReference actual constructor(internal actual val nativeValu override fun toString(): String = nativeValue.toString() } -actual open class Query(open val ios: FIRQuery) { +actual typealias NativeQuery = FIRQuery + +actual open class Query internal actual constructor(nativeQuery: NativeQuery) { + + open val ios: FIRQuery = nativeQuery actual suspend fun get() = QuerySnapshot(awaitResult { ios.getDocumentsWithCompletion(it) }) @@ -328,12 +306,12 @@ actual open class Query(open val ios: FIRQuery) { } @Suppress("UNCHECKED_CAST") -actual class CollectionReference(override val ios: FIRCollectionReference) : Query(ios) { +actual class CollectionReference(override val ios: FIRCollectionReference) : BaseCollectionReference(ios) { actual val path: String get() = ios.path - actual val async = Async(ios) + override val async = Async(ios) actual val document get() = DocumentReference(ios.documentWithAutoID()) @@ -341,19 +319,8 @@ actual class CollectionReference(override val ios: FIRCollectionReference) : Que actual fun document(documentPath: String) = DocumentReference(ios.documentWithPath(documentPath)) - actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = - DocumentReference(await { ios.addDocumentWithData(encode(data, encodeSettings) as Map, it) }) - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - DocumentReference(await { ios.addDocumentWithData(encode(strategy, data, encodeSettings) as Map, it) }) - - actual class Async(@PublishedApi internal val ios: FIRCollectionReference) { - actual inline fun add(data: T, encodeSettings: EncodeSettings) = - deferred { ios.addDocumentWithData(encode(data, encodeSettings) as Map, it) } - .convert(::DocumentReference) - - actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - deferred { ios.addDocumentWithData(encode(strategy, data, encodeSettings) as Map, it) } - .convert(::DocumentReference) + class Async(@PublishedApi internal val ios: FIRCollectionReference) : BaseCollectionReference.Async() { + override fun addEncoded(data: Any): Deferred = deferred { ios.addDocumentWithData(data as Map, it) }.convert(::DocumentReference) } } @@ -435,32 +402,20 @@ actual class DocumentChange(val ios: FIRDocumentChange) { get() = ChangeType.values().first { it.ios == ios.type } } -@Suppress("UNCHECKED_CAST") -actual class DocumentSnapshot(val ios: FIRDocumentSnapshot) { +actual class DocumentSnapshot(val ios: FIRDocumentSnapshot) : BaseDocumentSnapshot() { actual val id get() = ios.documentID actual val reference get() = DocumentReference(ios.reference) - actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T { - val data = ios.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) - return decode(value = data?.mapValues { (_, value) -> value?.takeIf { it !is NSNull } }) - } - - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T { - val data = ios.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) - return decode(strategy, data?.mapValues { (_, value) -> value?.takeIf { it !is NSNull } }, decodeSettings) - } - - actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior): T { - val value = ios.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - return decode(value) - } + override fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = + ios.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T { - val value = ios.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - return decode(strategy, value, decodeSettings) - } + override 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 @@ -506,7 +461,7 @@ private fun T.throwError(block: T.(errorPointer: CPointer awaitResult(function: (callback: (T?, NSError?) -> Unit) -> Unit): T { val job = CompletableDeferred() function { result, error -> - if(error == null) { + if(error == null) { job.complete(result) } else { job.completeExceptionally(error.toException()) 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 bb93b5c13..99846dad3 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 @@ -105,40 +105,9 @@ actual class WriteBatch(val js: JsWriteBatch) : BaseWriteBatch() { setOptions: SetOptions ): BaseWriteBatch = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } - @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List>, - merge: Boolean - ): BaseWriteBatch = rethrow { - val serializedItem = encodedData as Json - val serializedFieldAndValues = json(*encodedFieldsAndValues.toTypedArray()) - - val result = serializedItem.add(serializedFieldAndValues) - if (merge) { - js.set(documentRef.js, result, json("merge" to merge)) - } else { - js.set(documentRef.js, result) - } - }.let { this } - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = rethrow { js.update(documentRef.js, encodedData) } .let { this } - @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") - override fun updateEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List> - ): BaseWriteBatch = rethrow { - val serializedItem = encodedData as Json - val serializedFieldAndValues = json(*encodedFieldsAndValues.toTypedArray()) - - val result = serializedItem.add(serializedFieldAndValues) - js.update(documentRef.js, result) - }.let { this } - override fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> @@ -277,7 +246,11 @@ actual class DocumentReference actual constructor(internal actual val nativeValu } } -actual open class Query(open val js: JsQuery) { +actual typealias NativeQuery = JsQuery + +actual open class Query internal actual constructor(nativeQuery: NativeQuery) { + + open val js: JsQuery = nativeQuery actual suspend fun get() = rethrow { QuerySnapshot(getDocs(js).await()) } @@ -369,11 +342,11 @@ actual open class Query(open val js: JsQuery) { } } -actual class CollectionReference(override val js: JsCollectionReference) : Query(js) { +actual class CollectionReference(override val js: JsCollectionReference) : BaseCollectionReference(js) { actual val path: String get() = rethrow { js.path } - actual val async = Async(js) + override val async = Async(js) actual val document get() = rethrow { DocumentReference(doc(js)) } @@ -381,23 +354,11 @@ actual class CollectionReference(override val js: JsCollectionReference) : Query actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } - actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = - rethrow { DocumentReference(addDoc(js, encode(data, encodeSettings)!!).await()) } - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - rethrow { DocumentReference(addDoc(js, encode(strategy, data, encodeSettings)!!).await()) } + class Async(@PublishedApi internal val js: JsCollectionReference) : BaseCollectionReference.Async() { - @Suppress("DeferredIsResult") - actual class Async(@PublishedApi internal val js: JsCollectionReference) { - actual inline fun add(data: T, encodeSettings: EncodeSettings) = - rethrow { - addDoc(js, encode(data, encodeSettings)!!).asDeferred() - .convert(::DocumentReference) - } - actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - rethrow { - addDoc(js, encode(strategy, data, encodeSettings)!!).asDeferred() - .convert(::DocumentReference) - } + override fun addEncoded(data: Any): Deferred = rethrow { + addDoc(js, data).asDeferred().convert(::DocumentReference) + } } } @@ -425,22 +386,18 @@ actual class DocumentChange(val js: JsDocumentChange) { get() = ChangeType.values().first { it.jsString == js.type } } -actual class DocumentSnapshot(val js: JsDocumentSnapshot) { +actual class DocumentSnapshot(val js: JsDocumentSnapshot) : BaseDocumentSnapshot() { actual val id get() = rethrow { js.id } actual val reference get() = rethrow { DocumentReference(js.ref) } - actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T = - rethrow { decode(value = js.data(getTimestampsOptions(serverTimestampBehavior))) } - - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = - rethrow { decode(strategy, js.data(getTimestampsOptions(serverTimestampBehavior)), decodeSettings) } - - actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior) = - rethrow { decode(value = js.get(field, getTimestampsOptions(serverTimestampBehavior))) } + override fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.get(field, getTimestampsOptions(serverTimestampBehavior)) + } - actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior) = - rethrow { decode(strategy, js.get(field, getTimestampsOptions(serverTimestampBehavior)), decodeSettings) } + override 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() } 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 531846b44..9151cf1b9 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 @@ -14,7 +14,10 @@ expect class FirebaseFunctions { } abstract class BaseHttpsCallableReference { + suspend inline operator fun invoke(data: T, encodeDefaults: Boolean) = invoke(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) suspend inline operator fun invoke(data: T, encodeSettings: EncodeSettings = EncodeSettings()): HttpsCallableResult = invoke(encode(data, encodeSettings)!!) + + suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): HttpsCallableResult = invoke(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): HttpsCallableResult = invoke(encode(strategy, data, encodeSettings)!!) abstract suspend fun invoke(encodedData: Any): HttpsCallableResult } From fae5299e60ed730a128de9025442e17481190a7d Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 3 Jan 2024 16:07:49 +0100 Subject: [PATCH 03/27] Track for JS --- .../gitlive/firebase/firestore/firestore.kt | 1 + .../firebase/firestore/FieldValueTests.kt | 1 + .../gitlive/firebase/firestore/firestore.kt | 8 +- .../gitlive/firebase/firestore/firestore.kt | 8 +- .../gitlive/firebase/firestore/FieldValue.kt | 26 ++ .../gitlive/firebase/firestore/_encoders.kt | 10 + .../gitlive/firebase/firestore/firestore.kt | 316 +++++++----------- 7 files changed, 166 insertions(+), 204 deletions(-) create mode 100644 firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt create mode 100644 firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 918da3292..13e4c1c07 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 @@ -5,6 +5,7 @@ @file:JvmName("android") package dev.gitlive.firebase.firestore +import com.google.android.gms.tasks.Task import com.google.firebase.firestore.* import dev.gitlive.firebase.* import kotlinx.coroutines.runBlocking 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 571647ccf..ad4b5374b 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,5 +1,6 @@ package dev.gitlive.firebase.firestore +import dev.gitlive.firebase.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/firestore.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index aff733d31..3944b23dc 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 @@ -4,12 +4,15 @@ package dev.gitlive.firebase.firestore +import dev.gitlive.firebase.EncodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps +import dev.gitlive.firebase.decode import dev.gitlive.firebase.initialize import dev.gitlive.firebase.runBlockingTest import dev.gitlive.firebase.runTest +import dev.gitlive.firebase.withSerializer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.delay @@ -728,7 +731,7 @@ class FirebaseFirestoreTest { fun encodeDocumentReference() = runTest { val doc = firestore.document("a/b") val item = TestDataWithDocumentReference("123", doc, doc) - val encoded = encodedAsMap(encode(item, shouldEncodeElementDefault = false)) + val encoded = encodedAsMap(encode(item, EncodeSettings(shouldEncodeElementDefault = false))) assertEquals("123", encoded["uid"]) assertEquals(doc.nativeValue, encoded["reference"]) assertEquals(doc.nativeValue, encoded["optionalReference"]) @@ -737,7 +740,7 @@ class FirebaseFirestoreTest { @Test fun encodeNullDocumentReference() = runTest { val item = TestDataWithOptionalDocumentReference(null) - val encoded = encodedAsMap(encode(item, shouldEncodeElementDefault = false)) + val encoded = encodedAsMap(encode(item, EncodeSettings(shouldEncodeElementDefault = false))) assertNull(encoded["optionalReference"]) } @@ -791,7 +794,6 @@ class FirebaseFirestoreTest { assertNull(deletedList) } - private suspend fun setupFirestoreData() { @Test fun testQueryEqualTo() = runTest { setupFirestoreData() 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 6b373db3a..291bf59e1 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 @@ -246,11 +246,13 @@ actual class DocumentReference actual constructor(internal actual val nativeValu } } -actual typealias NativeQuery = JsQuery +actual data class NativeQuery(val js: JsQuery) actual open class Query internal actual constructor(nativeQuery: NativeQuery) { - open val js: JsQuery = nativeQuery + constructor(js: JsQuery) : this(NativeQuery(js)) + + open val js: JsQuery = nativeQuery.js actual suspend fun get() = rethrow { QuerySnapshot(getDocs(js).await()) } @@ -342,7 +344,7 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } } -actual class CollectionReference(override val js: JsCollectionReference) : BaseCollectionReference(js) { +actual class CollectionReference(override val js: JsCollectionReference) : BaseCollectionReference(NativeQuery(js)) { actual val path: String get() = rethrow { js.path } diff --git a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt new file mode 100644 index 000000000..f5f2cee34 --- /dev/null +++ b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt @@ -0,0 +1,26 @@ +package dev.gitlive.firebase.firestore + +import kotlinx.serialization.Serializable + +/** Represents a platform specific Firebase FieldValue. */ +typealias NativeFieldValue = com.google.firebase.firestore.FieldValue + +/** Represents a Firebase FieldValue. */ +@Serializable(with = FieldValueSerializer::class) +actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { + init { + require(nativeValue is NativeFieldValue) + } + override fun equals(other: Any?): Boolean = + this === other || other is FieldValue && nativeValue == other.nativeValue + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() + + actual companion object { + actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.serverTimestamp()) + actual val delete: FieldValue get() = FieldValue(NativeFieldValue.delete()) + actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.increment(value.toDouble())) + actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayUnion(*elements)) + actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayRemove(*elements)) + } +} diff --git a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt new file mode 100644 index 000000000..d12bda859 --- /dev/null +++ b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -0,0 +1,10 @@ +package dev.gitlive.firebase.firestore + +@PublishedApi +internal actual fun isSpecialValue(value: Any) = when(value) { + is NativeFieldValue, + is NativeGeoPoint, + is NativeTimestamp, + is NativeDocumentReference -> true + else -> false +} diff --git a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 13a16df0c..74a9b435c 100644 --- a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -5,13 +5,17 @@ @file:JvmName("android") package dev.gitlive.firebase.firestore +import com.google.android.gms.tasks.Task import com.google.firebase.firestore.* import dev.gitlive.firebase.* import dev.gitlive.firebase.firestore.FirebaseFirestoreException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.tasks.asDeferred import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.Serializable @@ -27,19 +31,14 @@ actual val Firebase.firestore get() = actual fun Firebase.firestore(app: FirebaseApp) = FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance(app.android)) -/** Helper method to perform an update operation. */ -@JvmName("performUpdateFields") -private fun performUpdate( - fieldsAndValues: Array>, - update: (String, Any?, Array) -> R -) = performUpdate(fieldsAndValues, { it }, { encode(it, true) }, update) - -/** Helper method to perform an update operation. */ -@JvmName("performUpdateFieldPaths") -private fun performUpdate( - fieldsAndValues: Array>, - update: (com.google.firebase.firestore.FieldPath, Any?, Array) -> R -) = performUpdate(fieldsAndValues, { it.android }, { encode(it, true) }, update) +@Suppress("DeferredIsResult") +@PublishedApi +internal fun Task.asUnitDeferred(): Deferred = CompletableDeferred() + .apply { + asDeferred().invokeOnCompletion { exception -> + if (exception == null) complete(Unit) else completeExceptionally(exception) + } + } actual class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) { @@ -84,114 +83,84 @@ actual class FirebaseFirestore(val android: com.google.firebase.firestore.Fireba } -actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) { - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(documentRef.android, encode(data, encodeDefaults)!!) - }.let { this } - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .let { this } - - actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!) - }.let { this } +val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { + is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() + is SetOptions.Overwrite -> null + is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) + is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) +} - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .let { this } +actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) : BaseWriteBatch() { - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .let { this } + actual val async = Async(android) - @Suppress("UNCHECKED_CAST") - actual inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = - android.update(documentRef.android, encode(data, encodeDefaults) as Map).let { this } + override fun setEncoded( + documentRef: DocumentReference, + encodedData: Any, + setOptions: SetOptions + ): BaseWriteBatch = (setOptions.android?.let { + android.set(documentRef.android, encodedData, it) + } ?: android.set(documentRef.android, encodedData)).let { + this + } @Suppress("UNCHECKED_CAST") - actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - android.update(documentRef.android, encode(strategy, data, encodeDefaults) as Map).let { this } + override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = android.update(documentRef.android, encodedData as Map).let { this } - @JvmName("updateFields") - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } + override fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseWriteBatch = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + android.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } - @JvmName("updateFieldPaths") - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } + override fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseWriteBatch = 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().run { Unit } + actual suspend fun commit() = async.commit().await() + @Suppress("DeferredIsResult") + actual class Async(private val android: com.google.firebase.firestore.WriteBatch) { + actual fun commit(): Deferred = android.commit().asUnitDeferred() + } } -actual class Transaction(val android: com.google.firebase.firestore.Transaction) { +actual class Transaction(val android: com.google.firebase.firestore.Transaction) : BaseTransaction() { - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(documentRef.android, encode(data, encodeDefaults)!!) - }.let { this } - - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .let { this } - - actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(documentRef.android, encode(data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .let { this } - - actual fun set( + override fun setEncoded( documentRef: DocumentReference, - strategy: SerializationStrategy, - data: T, - encodeDefaults: Boolean, - merge: Boolean - ) = when(merge) { - true -> android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!) - }.let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .let { this } - - actual fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(documentRef.android, encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .let { this } - - @Suppress("UNCHECKED_CAST") - actual fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = - android.update(documentRef.android, encode(data, encodeDefaults) as Map).let { this } + encodedData: Any, + setOptions: SetOptions + ): BaseTransaction { + setOptions.android?.let { + android.set(documentRef.android, encodedData, it) + } ?: android.set(documentRef.android, encodedData) + return this + } @Suppress("UNCHECKED_CAST") - actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - android.update(documentRef.android, encode(strategy, data, encodeDefaults) as Map).let { this } + override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = android.update(documentRef.android, encodedData as Map).let { this } - @JvmName("updateFields") - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } + override fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): BaseTransaction = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + android.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } - @JvmName("updateFieldPaths") - actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } + override 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 } @@ -204,7 +173,7 @@ actual class Transaction(val android: com.google.firebase.firestore.Transaction) actual typealias NativeDocumentReference = com.google.firebase.firestore.DocumentReference @Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) { +actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { val android: NativeDocumentReference by ::nativeValue actual val id: String get() = android.id @@ -215,58 +184,9 @@ actual class DocumentReference actual constructor(internal actual val nativeValu actual val parent: CollectionReference get() = CollectionReference(android.parent) - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) - - actual suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(encode(data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(encode(data, encodeDefaults)!!) - }.await().run { Unit } - - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(encode(data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .await().run { Unit } + override val async = Async(android) - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(encode(data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .await().run { Unit } - - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = when(merge) { - true -> android.set(encode(strategy, data, encodeDefaults)!!, SetOptions.merge()) - false -> android.set(encode(strategy, data, encodeDefaults)!!) - }.await().run { Unit } - - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - android.set(encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFields(*mergeFields)) - .await().run { Unit } - - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - android.set(encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) - .await().run { Unit } - - @Suppress("UNCHECKED_CAST") - actual suspend inline fun update(data: T, encodeDefaults: Boolean) = - android.update(encode(data, encodeDefaults) as Map).await().run { Unit } - - @Suppress("UNCHECKED_CAST") - actual suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - android.update(encode(strategy, data, encodeDefaults) as Map).await().run { Unit } - - @JvmName("updateFields") - actual suspend fun update(vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(field, value, *moreFieldsAndValues) - }?.await() - .run { Unit } - - @JvmName("updateFieldPaths") - actual suspend fun update(vararg fieldsAndValues: Pair) = - performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - android.update(field, value, *moreFieldsAndValues) - }?.await() - .run { Unit } - - actual suspend fun delete() = - android.delete().await().run { Unit } + actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) actual suspend fun get() = DocumentSnapshot(android.get().await()) @@ -285,9 +205,36 @@ actual class DocumentReference actual constructor(internal actual val nativeValu this === other || other is DocumentReference && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() + + @Suppress("DeferredIsResult") + class Async(@PublishedApi internal val android: NativeDocumentReference) : BaseDocumentReference.Async() { + + override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = (setOptions.android?.let { + android.set(encodedData, it) + } ?: android.set(encodedData)).asUnitDeferred() + + @Suppress("UNCHECKED_CAST") + override fun updateEncoded(encodedData: Any): Deferred = android.update(encodedData as Map).asUnitDeferred() + + override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { + android.update(encodedFieldsAndValues.toMap()) + }?.asUnitDeferred() ?: CompletableDeferred(Unit) + + override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + android.update(field, value, *moreFieldsAndValues) + }?.asUnitDeferred() ?: CompletableDeferred(Unit) + + override fun delete() = + android.delete().asUnitDeferred() + } } -actual open class Query(open val android: AndroidQuery) { +actual typealias NativeQuery = AndroidQuery + +actual open class Query internal actual constructor(nativeQuery: NativeQuery) { + + open val android = nativeQuery actual suspend fun get() = QuerySnapshot(android.get().await()) @@ -399,10 +346,11 @@ actual open class Query(open val android: AndroidQuery) { actual typealias Direction = com.google.firebase.firestore.Query.Direction actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type -actual class CollectionReference(override val android: com.google.firebase.firestore.CollectionReference) : Query(android) { +actual class CollectionReference(override val android: com.google.firebase.firestore.CollectionReference) : BaseCollectionReference(android) { actual val path: String get() = android.path + override val async = Async(android) actual val document: DocumentReference get() = DocumentReference(android.document()) @@ -412,13 +360,10 @@ actual class CollectionReference(override val android: com.google.firebase.fires actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) - actual suspend inline fun add(data: T, encodeDefaults: Boolean) = - DocumentReference(android.add(encode(data, encodeDefaults)!!).await()) - - actual suspend fun add(data: T, strategy: SerializationStrategy, encodeDefaults: Boolean) = - DocumentReference(android.add(encode(strategy, data, encodeDefaults)!!).await()) - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - DocumentReference(android.add(encode(strategy, data, encodeDefaults)!!).await()) + @Suppress("DeferredIsResult") + class Async(@PublishedApi internal val android: com.google.firebase.firestore.CollectionReference) : BaseCollectionReference.Async() { + override fun addEncoded(data: Any): Deferred = android.add(data).asDeferred().convert(::DocumentReference) + } } actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException @@ -447,22 +392,13 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC } @Suppress("UNCHECKED_CAST") -actual class DocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) { +actual class DocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) : BaseDocumentSnapshot() { actual val id get() = android.id actual val reference get() = DocumentReference(android.reference) - actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T = - decode(value = android.getData(serverTimestampBehavior.toAndroid())) - - actual fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.getData(serverTimestampBehavior.toAndroid())) - - actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(value = android.get(field, serverTimestampBehavior.toAndroid())) - - actual fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.get(field, serverTimestampBehavior.toAndroid())) + override fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = android.get(field, serverTimestampBehavior.toAndroid()) + override fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = android.getData(serverTimestampBehavior.toAndroid()) actual fun contains(field: String) = android.contains(field) @@ -483,33 +419,17 @@ actual class SnapshotMetadata(val android: com.google.firebase.firestore.Snapsho } actual class FieldPath private constructor(val android: com.google.firebase.firestore.FieldPath) { - actual constructor(vararg fieldNames: String) : this(com.google.firebase.firestore.FieldPath.of(*fieldNames)) - actual val documentId: FieldPath get() = FieldPath(com.google.firebase.firestore.FieldPath.documentId()) + actual constructor(vararg fieldNames: String) : this( + com.google.firebase.firestore.FieldPath.of( + *fieldNames + ) + ) + actual val documentId: FieldPath get() = FieldPath(com.google.firebase.firestore.FieldPath.documentId()) + actual val encoded: EncodedFieldPath = android override fun equals(other: Any?): Boolean = other is FieldPath && android == other.android override fun hashCode(): Int = android.hashCode() override fun toString(): String = android.toString() } -/** Represents a platform specific Firebase FieldValue. */ -private typealias NativeFieldValue = com.google.firebase.firestore.FieldValue - -/** Represents a Firebase FieldValue. */ -@Serializable(with = FieldValueSerializer::class) -actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { - init { - require(nativeValue is NativeFieldValue) - } - override fun equals(other: Any?): Boolean = - this === other || other is FieldValue && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - actual companion object { - actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.serverTimestamp()) - actual val delete: FieldValue get() = FieldValue(NativeFieldValue.delete()) - actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.increment(value.toDouble())) - actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayUnion(*elements)) - actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayRemove(*elements)) - } -} +actual typealias EncodedFieldPath = com.google.firebase.firestore.FieldPath From ba4a8b5fe277df2bb5df1a346b995e2a33c5e178 Mon Sep 17 00:00:00 2001 From: Nicholas Bransby-Williams Date: Thu, 11 Jan 2024 00:01:22 +0200 Subject: [PATCH 04/27] Update README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7f2d99213..dff3de3e4 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,16 @@ The following libraries are available for the various Firebase products. | Service or Product | Gradle Dependency | API Coverage | |---------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [Authentication](https://firebase.google.com/docs/auth) | [`dev.gitlive:firebase-auth:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/1.10.4/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) | -| [Realtime Database](https://firebase.google.com/docs/database) | [`dev.gitlive:firebase-database:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-database/1.10.4/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) | -| [Cloud Firestore](https://firebase.google.com/docs/firestore) | [`dev.gitlive:firebase-firestore:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/1.10.4/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) | -| [Cloud Functions](https://firebase.google.com/docs/functions) | [`dev.gitlive:firebase-functions:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-functions/1.10.4/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt) | -| [Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) | [`dev.gitlive:firebase-messaging:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-messaging/1.10.4/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | -| [Cloud Storage](https://firebase.google.com/docs/storage) | [`dev.gitlive:firebase-storage:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-storage/1.10.4/pom) | [![40%](https://img.shields.io/badge/-40%25-orange?style=flat-square)](/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt) | -| [Installations](https://firebase.google.com/docs/projects/manage-installations) | [`dev.gitlive:firebase-installations:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-installations/1.10.4/pom) | [![90%](https://img.shields.io/badge/-90%25-green?style=flat-square)](/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt) | -| [Remote Config](https://firebase.google.com/docs/remote-config) | [`dev.gitlive:firebase-config:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-config/1.10.4/pom) | [![20%](https://img.shields.io/badge/-20%25-orange?style=flat-square)](/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt) | -| [Performance](https://firebase.google.com/docs/perf-mon) | [`dev.gitlive:firebase-perf:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-perf/1.10.4/pom) | [![1%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt) | -| [Crashlytics](https://firebase.google.com/docs/crashlytics) | [`dev.gitlive:firebase-crashlytics:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-crashlytics/1.10.4/pom) | [![80%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt) | +| [Authentication](https://firebase.google.com/docs/auth) | [`dev.gitlive:firebase-auth:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/1.11.0/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) | +| [Realtime Database](https://firebase.google.com/docs/database) | [`dev.gitlive:firebase-database:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-database/1.11.0/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) | +| [Cloud Firestore](https://firebase.google.com/docs/firestore) | [`dev.gitlive:firebase-firestore:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/1.11.0/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) | +| [Cloud Functions](https://firebase.google.com/docs/functions) | [`dev.gitlive:firebase-functions:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-functions/1.11.0/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt) | +| [Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) | [`dev.gitlive:firebase-messaging:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-messaging/1.11.0/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | +| [Cloud Storage](https://firebase.google.com/docs/storage) | [`dev.gitlive:firebase-storage:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-storage/1.11.0/pom) | [![40%](https://img.shields.io/badge/-40%25-orange?style=flat-square)](/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt) | +| [Installations](https://firebase.google.com/docs/projects/manage-installations) | [`dev.gitlive:firebase-installations:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-installations/1.11.0/pom) | [![90%](https://img.shields.io/badge/-90%25-green?style=flat-square)](/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt) | +| [Remote Config](https://firebase.google.com/docs/remote-config) | [`dev.gitlive:firebase-config:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-config/1.11.0/pom) | [![20%](https://img.shields.io/badge/-20%25-orange?style=flat-square)](/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt) | +| [Performance](https://firebase.google.com/docs/perf-mon) | [`dev.gitlive:firebase-perf:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-perf/1.11.0/pom) | [![1%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt) | +| [Crashlytics](https://firebase.google.com/docs/crashlytics) | [`dev.gitlive:firebase-crashlytics:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-crashlytics/1.11.0/pom) | [![80%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt) | Is the Firebase library or API you need missing? [Create an issue](https://github.com/GitLiveApp/firebase-kotlin-sdk/issues/new?labels=API+coverage&template=increase-api-coverage.md&title=Add+%5Bclass+name%5D.%5Bfunction+name%5D+to+%5Blibrary+name%5D+for+%5Bplatform+names%5D) to request additional API coverage or be awesome and [submit a PR](https://github.com/GitLiveApp/firebase-kotlin-sdk/fork) From 43a38ca08357fbf3c3321fc8c444aedf2f37d0a0 Mon Sep 17 00:00:00 2001 From: nbransby Date: Fri, 12 Jan 2024 16:18:10 +1100 Subject: [PATCH 05/27] update to firebase-java-sdk:0.4.0 --- build.gradle.kts | 2 +- firebase-auth/package.json | 4 ++-- gradle.properties | 22 +++++++++++----------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0d36ab044..62966582e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -117,7 +117,7 @@ subprojects { "commonTestImplementation"(kotlin("test-common")) "commonTestImplementation"(kotlin("test-annotations-common")) if (this@afterEvaluate.name != "firebase-crashlytics") { - "jvmMainApi"("dev.gitlive:firebase-java-sdk:0.3.0") + "jvmMainApi"("dev.gitlive:firebase-java-sdk:0.4.0") "jvmMainApi"("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutinesVersion") { exclude("com.google.android.gms") } diff --git a/firebase-auth/package.json b/firebase-auth/package.json index be336c079..7f21dc747 100644 --- a/firebase-auth/package.json +++ b/firebase-auth/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-auth", - "version": "1.11.0", + "version": "1.12.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-auth.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.0", + "@gitlive/firebase-app": "1.12.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/gradle.properties b/gradle.properties index 53a73a811..3fb471e2b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -47,17 +47,17 @@ firebase-perf.skipJsTests=false firebase-storage.skipJsTests=false # Versions: -firebase-app.version=1.11.0 -firebase-auth.version=1.11.0 -firebase-common.version=1.11.0 -firebase-config.version=1.11.0 -firebase-database.version=1.11.0 -firebase-firestore.version=1.11.0 -firebase-functions.version=1.11.0 -firebase-installations.version=1.11.0 -firebase-perf.version=1.11.0 -firebase-crashlytics.version=1.11.0 -firebase-storage.version=1.11.0 +firebase-app.version=1.12.0 +firebase-auth.version=1.12.0 +firebase-common.version=1.12.0 +firebase-config.version=1.12.0 +firebase-database.version=1.12.0 +firebase-firestore.version=1.12.0 +firebase-functions.version=1.12.0 +firebase-installations.version=1.12.0 +firebase-perf.version=1.12.0 +firebase-crashlytics.version=1.12.0 +firebase-storage.version=1.12.0 # Dependencies Versions: gradlePluginVersion=8.1.3 From 0ba025c0a7e1fef223b263a65b8571aa89467dd7 Mon Sep 17 00:00:00 2001 From: Andrew Reed Date: Fri, 12 Jan 2024 14:11:33 +0000 Subject: [PATCH 06/27] Fixed issue where crashlytics would put different crashes under the same crash --- .../kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-crashlytics/src/iosMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt b/firebase-crashlytics/src/iosMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt index 209e82f9f..65cb6cbd8 100644 --- a/firebase-crashlytics/src/iosMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt +++ b/firebase-crashlytics/src/iosMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt @@ -43,5 +43,5 @@ private fun Throwable.asNSError(): NSError { if (message != null) { userInfo[NSLocalizedDescriptionKey] = message } - return NSError.errorWithDomain("KotlinException", 0, userInfo) + return NSError.errorWithDomain(this::class.qualifiedName, 0, userInfo) } \ No newline at end of file From a4a7150d0d35db6e787db9846ec5764911b258af Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 12 Jan 2024 17:34:35 +0100 Subject: [PATCH 07/27] Use named arguments for settings --- README.md | 8 + .../kotlin/dev/gitlive/firebase/decoders.kt | 10 +- .../kotlin/dev/gitlive/firebase/encoders.kt | 7 +- .../dev/gitlive/firebase/EncodersTest.kt | 4 +- firebase-crashlytics/package.json | 2 +- .../dev/gitlive/firebase/database/database.kt | 17 +- .../dev/gitlive/firebase/database/database.kt | 41 ++-- .../dev/gitlive/firebase/database/database.kt | 27 ++- .../dev/gitlive/firebase/database/database.kt | 15 +- .../gitlive/firebase/firestore/encoders.kt | 7 +- .../gitlive/firebase/firestore/firestore.kt | 175 ++++++++---------- .../dev/gitlive/firebase/firestore/helpers.kt | 13 +- .../gitlive/firebase/firestore/firestore.kt | 8 +- .../gitlive/firebase/functions/functions.kt | 5 +- .../gitlive/firebase/functions/functions.kt | 19 +- .../gitlive/firebase/functions/functions.kt | 6 +- .../gitlive/firebase/functions/functions.kt | 5 +- firebase-installations/package.json | 2 +- firebase-perf/package.json | 2 +- firebase-storage/package.json | 2 +- 20 files changed, 192 insertions(+), 183 deletions(-) diff --git a/README.md b/README.md index 7f2d99213..a77d1b28f 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,14 @@ citiesRef.where { } ``` +

Named arguments

+ +To improve readability functions such as the Cloud Firestore data encoding/decoding use named arguments: + +```kotlin +documentRef.set(data, encodeDefaults = false, serializersModule = customSerializerModule) +``` +

Operator overloading

In cases where it makes sense, such as Firebase Functions HTTPS Callable, operator overloading is used: diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt index a292c249d..2654d2c1d 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt @@ -15,15 +15,13 @@ import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer -inline fun decode(value: Any?): T = decode(value, DecodeSettings()) -inline fun decode(value: Any?, settings: DecodeSettings): T { +inline fun decode(value: Any?, serializersModule: SerializersModule = EmptySerializersModule()): T { val strategy = serializer() - return decode(strategy as DeserializationStrategy, value, settings) + return decode(strategy as DeserializationStrategy, value, serializersModule) } -fun decode(strategy: DeserializationStrategy, value: Any?): T = decode(strategy, value, DecodeSettings()) -fun decode(strategy: DeserializationStrategy, value: Any?, settings: DecodeSettings): T { +fun decode(strategy: DeserializationStrategy, value: Any?, serializersModule: SerializersModule = EmptySerializersModule()): T { require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" } - return FirebaseDecoder(value, settings).decodeSerializableValue(strategy) + return FirebaseDecoder(value, DecodeSettings(serializersModule)).decodeSerializableValue(strategy) } expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder expect fun getPolymorphicType(value: Any?, discriminator: String): String diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index e10db675b..7c3e3c9ed 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -7,14 +7,15 @@ package dev.gitlive.firebase import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule -fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = encode(strategy, value, EncodeSettings(shouldEncodeElementDefault)) +fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean, serializersModule: SerializersModule = EmptySerializersModule()): Any? = encode(strategy, value, EncodeSettings(shouldEncodeElementDefault, serializersModule)) fun encode(strategy: SerializationStrategy, value: T, settings: EncodeSettings): Any? = FirebaseEncoder(settings).apply { encodeSerializableValue(strategy, value) }.value -inline fun encode(value: T, shouldEncodeElementDefault: Boolean): Any? = encode(value, EncodeSettings(shouldEncodeElementDefault)) +inline fun encode(value: T, shouldEncodeElementDefault: Boolean, serializersModule: SerializersModule = EmptySerializersModule()): Any? = encode(value, EncodeSettings(shouldEncodeElementDefault, serializersModule)) inline fun encode(value: T, settings: EncodeSettings): Any? = value?.let { FirebaseEncoder(settings).apply { if (it is ValueWithSerializer<*> && it.value is T) { @@ -42,7 +43,7 @@ class FirebaseEncoder( internal val settings: EncodeSettings ) : Encoder { -// constructor(shouldEncodeElementDefault: Boolean) : this(EncodeSettings(shouldEncodeElementDefault)) + constructor(shouldEncodeElementDefault: Boolean, serializersModule: SerializersModule = EmptySerializersModule()) : this(EncodeSettings(shouldEncodeElementDefault, serializersModule)) var value: Any? = null diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index a26b99ea1..c43b5ce39 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -145,7 +145,7 @@ class EncodersTest { nativeAssertEquals(nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true), encoded) - val decoded = decode(AbstractClass.serializer(), encoded, DecodeSettings(module)) + val decoded = decode(AbstractClass.serializer(), encoded, module) assertEquals(abstractClass, decoded) } @@ -174,7 +174,7 @@ class EncodersTest { encoded ) - val decoded = decode(NestedClass.serializer(), encoded, DecodeSettings(module)) + val decoded = decode(NestedClass.serializer(), encoded, module) assertEquals(nestedClass, decoded) } } diff --git a/firebase-crashlytics/package.json b/firebase-crashlytics/package.json index 360f26f85..2e03d9beb 100644 --- a/firebase-crashlytics/package.json +++ b/firebase-crashlytics/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-crashlytics", - "version": "1.11.4", + "version": "1.11.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-crashlytics.js", "scripts": { 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 ddd18f708..ca7f2508d 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 @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer +import kotlinx.serialization.modules.SerializersModule import java.util.* import kotlin.time.Duration.Companion.seconds @@ -183,8 +184,8 @@ actual class DatabaseReference internal constructor( .run { Unit } @Suppress("UNCHECKED_CAST") - override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = - android.updateChildren(encode(update, encodeSettings) as Map) + override suspend fun updateChildren(update: Map, encodeDefaults: Boolean, serializersModule: SerializersModule) = + android.updateChildren(encode(update, encodeDefaults, serializersModule) as Map) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @@ -192,13 +193,13 @@ actual class DatabaseReference internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, serializersModule: SerializersModule, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() android.runTransaction(object : Transaction.Handler { override fun doTransaction(currentData: MutableData): Transaction.Result { currentData.value = currentData.value?.let { - transactionUpdate(decode(strategy, it, decodeSettings)) + transactionUpdate(decode(strategy, it, serializersModule)) } return Transaction.success(currentData) } @@ -236,8 +237,8 @@ actual class DataSnapshot internal constructor( actual inline fun value() = decode(value = android.value) - actual fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - decode(strategy, android.value, decodeSettings) + actual fun value(strategy: DeserializationStrategy, serializersModule: SerializersModule) = + decode(strategy, android.value, serializersModule) actual fun child(path: String) = DataSnapshot(android.child(path), persistenceEnabled) actual val hasChildren get() = android.hasChildren() @@ -262,8 +263,8 @@ actual class OnDisconnect internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = - android.updateChildren(update.mapValues { (_, it) -> encode(it, encodeSettings) }) + override suspend fun updateChildren(update: Map, encodeDefaults: Boolean, serializersModule: SerializersModule) = + android.updateChildren(update.mapValues { (_, it) -> encode(it, encodeDefaults, serializersModule) }) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } } diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index 97a6caccc..dcde429a4 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 @@ -4,16 +4,19 @@ package dev.gitlive.firebase.database -import dev.gitlive.firebase.DecodeSettings -import dev.gitlive.firebase.EncodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.database.ChildEvent.Type.* +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 kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule /** Returns the [FirebaseDatabase] instance of the default [FirebaseApp]. */ expect val Firebase.database: FirebaseDatabase @@ -70,17 +73,13 @@ expect open class Query internal constructor(nativeQuery: NativeQuery) { } abstract class BaseDatabaseReference internal constructor(nativeQuery: NativeQuery) : Query(nativeQuery) { - suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = - setValue(value, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings = EncodeSettings()) = - setValueEncoded(encode(value, encodeSettings)) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = - setValue(strategy, value, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) = setValueEncoded(encode(strategy, value, encodeSettings)) + suspend inline fun setValue(value: T?, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + setValueEncoded(encode(value, encodeDefaults, serializersModule)) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + setValueEncoded(encode(strategy, value, encodeDefaults, serializersModule)) abstract suspend fun setValueEncoded(encodedValue: Any?) - suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - abstract suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) + abstract suspend fun updateChildren(update: Map, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) } expect class DatabaseReference : BaseDatabaseReference { @@ -91,7 +90,7 @@ expect class DatabaseReference : BaseDatabaseReference { suspend fun removeValue() - suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings = DecodeSettings(), transactionUpdate: (currentData: T) -> T): DataSnapshot + suspend fun runTransaction(strategy: KSerializer, serializersModule: SerializersModule = EmptySerializersModule(), transactionUpdate: (currentData: T) -> T): DataSnapshot } expect class DataSnapshot { @@ -100,7 +99,7 @@ expect class DataSnapshot { val ref: DatabaseReference val value: Any? inline fun value(): T - fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings()): T + fun value(strategy: DeserializationStrategy, serializersModule: SerializersModule = EmptySerializersModule()): T fun child(path: String): DataSnapshot val hasChildren: Boolean val children: Iterable @@ -109,17 +108,13 @@ expect class DataSnapshot { expect class DatabaseException(message: String?, cause: Throwable?) : RuntimeException abstract class BaseOnDisconnect internal constructor() { - suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = - setValue(value, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings = EncodeSettings()) = - setValue(encode(value, encodeSettings)) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = - setValue(strategy, value, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) = setValue(encode(strategy, value, encodeSettings)) + suspend inline fun setValue(value: T?, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + setValue(encode(value, encodeDefaults, serializersModule)) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + setValue(encode(strategy, value, encodeDefaults, serializersModule)) abstract suspend fun setValue(encodedValue: Any?) - abstract suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) - suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) + abstract suspend fun updateChildren(update: Map, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) } expect class OnDisconnect : BaseOnDisconnect { 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 d64e6b04c..8884bc59b 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 @@ -20,6 +20,8 @@ import kotlinx.coroutines.selects.select import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule import platform.Foundation.NSError import platform.Foundation.allObjects import kotlin.collections.component1 @@ -150,21 +152,24 @@ actual class DatabaseReference internal constructor( ios.await(persistenceEnabled) { setValue(encodedValue, it) } } - @Suppress("UNCHECKED_CAST") - override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { updateChildValues(encode(update, encodeSettings) as Map, it) } + override suspend fun updateChildren( + update: Map, + encodeDefaults: Boolean, + serializersModule: SerializersModule + ) { + ios.await(persistenceEnabled) { updateChildValues(encode(update, encodeDefaults, serializersModule) as Map, it) } } actual suspend fun removeValue() { ios.await(persistenceEnabled) { removeValueWithCompletionBlock(it) } } - actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, serializersModule: SerializersModule, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() ios.runTransactionBlock( block = { firMutableData -> firMutableData?.value = firMutableData?.value?.let { - transactionUpdate(decode(strategy, it, decodeSettings)) + transactionUpdate(decode(strategy, it, serializersModule)) } FIRTransactionResult.successWithValue(firMutableData!!) }, @@ -198,8 +203,8 @@ actual class DataSnapshot internal constructor( actual inline fun value() = decode(value = ios.value) - actual fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - decode(strategy, ios.value, decodeSettings) + actual fun value(strategy: DeserializationStrategy, serializersModule: SerializersModule) = + decode(strategy, ios.value, serializersModule) actual fun child(path: String) = DataSnapshot(ios.childSnapshotForPath(path), persistenceEnabled) actual val hasChildren get() = ios.hasChildren() @@ -223,8 +228,12 @@ actual class OnDisconnect internal constructor( } @Suppress("UNCHECKED_CAST") - override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it, encodeSettings) } as Map, it) } + override suspend fun updateChildren( + update: Map, + encodeDefaults: Boolean, + serializersModule: SerializersModule + ) { + ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it, encodeDefaults, serializersModule) } as Map, it) } } } diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index e1456e9ed..ef6119487 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 @@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.produceIn import kotlinx.coroutines.selects.select import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer +import kotlinx.serialization.modules.SerializersModule import kotlin.js.Promise import kotlin.js.json import dev.gitlive.firebase.database.externals.DataSnapshot as JsDataSnapshot @@ -152,11 +153,11 @@ actual class DatabaseReference internal constructor( set(js, encodedValue).awaitWhileOnline(database) } - override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = - rethrow { update(js, encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } + override suspend fun updateChildren(update: Map, encodeDefaults: Boolean, serializersModule: SerializersModule) = + rethrow { update(js, encode(update, encodeDefaults, serializersModule) ?: json()).awaitWhileOnline(database) } - actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, serializersModule: SerializersModule, transactionUpdate: (currentData: T) -> T): DataSnapshot { return DataSnapshot(jsRunTransaction(js, transactionUpdate).awaitWhileOnline(database).snapshot, database) } } @@ -173,8 +174,8 @@ actual class DataSnapshot internal constructor( actual inline fun value() = rethrow { decode(value = js.`val`()) } - actual fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - rethrow { decode(strategy, js.`val`(), decodeSettings) } + actual fun value(strategy: DeserializationStrategy, serializersModule: SerializersModule) = + rethrow { decode(strategy, js.`val`(), serializersModule) } actual val exists get() = rethrow { js.exists() } actual val key get() = rethrow { js.key } @@ -201,8 +202,8 @@ actual class OnDisconnect internal constructor( override suspend fun setValue(encodedValue: Any?) = rethrow { js.set(encodedValue).awaitWhileOnline(database) } - override suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = - rethrow { js.update(encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } + override suspend fun updateChildren(update: Map, encodeDefaults: Boolean, serializersModule: SerializersModule) = + rethrow { js.update(encode(update, encodeDefaults, serializersModule) ?: json()).awaitWhileOnline(database) } } 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 21e9bbc64..6b506ebc9 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 @@ -1,15 +1,16 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.EncodeSettings +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule /** @return whether value is special and shouldn't be encoded/decoded. */ @PublishedApi internal expect fun isSpecialValue(value: Any): Boolean @PublishedApi -internal inline fun encode(value: T, encodeSettings: EncodeSettings) = +internal inline fun encode(value: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = if (value?.let(::isSpecialValue) == true) { value } else { - dev.gitlive.firebase.encode(value, encodeSettings) + dev.gitlive.firebase.encode(value, encodeDefaults, serializersModule) } 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 0212cb37d..7dcc63e15 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -10,6 +10,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule import kotlin.jvm.JvmName /** Returns the [FirebaseFirestore] instance of the default [FirebaseApp]. */ @@ -43,31 +45,24 @@ sealed class SetOptions { abstract class BaseTransaction { - fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean = false): BaseTransaction = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) - fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) - fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) - fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false): BaseTransaction = + setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) protected abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseTransaction - fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = update(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - fun update(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(data, encodeSettings)!!) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!) + fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!) + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!) @JvmName("updateFields") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) @JvmName("updateFieldPaths") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) protected abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction protected abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction @@ -181,39 +176,31 @@ internal val Any.safeValue: Any get() = when (this) { abstract class BaseWriteBatch { - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) - inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = - setEncoded(documentRef, encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) - inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = - setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) - inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = - setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = - setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = - setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = - setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = + setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = + setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = + setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = + setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = + setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = + setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseWriteBatch - inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = update(documentRef, data, EncodeSettings(encodeDefaults)) - inline fun update(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = - updateEncoded(documentRef, encode(data, encodeSettings)!!) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = - updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!) + inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + updateEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!) + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + updateEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!) @JvmName("updateField") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) @JvmName("updateFieldPath") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch @@ -239,26 +226,26 @@ expect class NativeDocumentReference abstract class BaseDocumentReference { abstract class Async { - inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = setEncoded(encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = setEncoded(encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = setEncoded(encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = setEncoded(encode(data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = setEncoded(encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = setEncoded(encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = setEncoded( - encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String)= setEncoded( - encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = setEncoded( - encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = setEncoded( + encode(strategy, data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String)= setEncoded( + encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = setEncoded( + encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) abstract fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred - inline fun update(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(encode(data, encodeSettings)!!) - fun update(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = update(encode(strategy, data, encodeSettings)) + inline fun update(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncoded(encode(data, encodeDefaults, serializersModule)!!) + fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = update(encode(strategy, data, encodeDefaults, serializersModule)) @JvmName("updateFields") - fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + fun update(vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) @JvmName("updateFieldPaths") - fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + fun update(vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) abstract fun updateEncoded(encodedData: Any): Deferred protected abstract fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred @@ -269,45 +256,37 @@ abstract class BaseDocumentReference { abstract val async: Async - suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) - suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = - async.set(data, encodeSettings, merge).await() + suspend inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = + async.set(data, encodeDefaults, serializersModule, merge).await() - suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) - suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = - async.set(data, encodeSettings, mergeFields = mergeFields).await() + suspend inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = + async.set(data, encodeDefaults, serializersModule, mergeFields = mergeFields).await() - suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) - suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = - async.set(data, encodeSettings, mergeFieldPaths = mergeFieldPaths).await() + suspend inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = + async.set(data, encodeDefaults, serializersModule, mergeFieldPaths = mergeFieldPaths).await() - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), merge) - suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = - async.set(strategy, data, encodeSettings, merge).await() + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = + async.set(strategy, data, encodeDefaults, serializersModule, merge).await() - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFields) - suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = - async.set(strategy, data, encodeSettings, mergeFields = mergeFields).await() + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = + async.set(strategy, data, encodeDefaults, serializersModule, mergeFields = mergeFields).await() - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults), *mergeFieldPaths) - suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = - async.set(strategy, data, encodeSettings, mergeFieldPaths = mergeFieldPaths).await() + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = + async.set(strategy, data, encodeDefaults, serializersModule, mergeFieldPaths = mergeFieldPaths).await() - suspend inline fun update(data: T, encodeDefaults: Boolean) = update(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend inline fun update(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = - async.update(data, encodeSettings).await() + suspend inline fun update(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + async.update(data, encodeDefaults, serializersModule).await() - suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend fun update(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = - async.update(strategy, data, encodeSettings).await() + suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + async.update(strategy, data, encodeDefaults, serializersModule).await() @JvmName("updateFields") - suspend fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = - async.update(fieldsAndValues = fieldsAndValues, encodeSettings).await() + suspend fun update(vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + async.update(fieldsAndValues = fieldsAndValues, encodeDefaults, serializersModule).await() @JvmName("updateFieldPaths") - suspend fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = - async.update(fieldsAndValues = fieldsAndValues, encodeSettings).await() + suspend fun update(vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + async.update(fieldsAndValues = fieldsAndValues, encodeDefaults, serializersModule).await() suspend fun delete() = async.delete().await() @@ -332,20 +311,20 @@ abstract class BaseCollectionReference(nativeQuery: NativeQuery) : Query(nativeQ @Suppress("DeferredIsResult") abstract class Async { - inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = addEncoded( - encode(data, encodeSettings)!! + inline fun add(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = addEncoded( + encode(data, encodeDefaults, serializersModule)!! ) - fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = addEncoded( - encode(strategy, data, encodeSettings)!! + fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = addEncoded( + encode(strategy, data, encodeDefaults, serializersModule)!! ) abstract fun addEncoded(data: Any): Deferred } - suspend inline fun add(data: T, encodeDefaults: Boolean) = add(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = async.add(data, encodeSettings).await() - suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = add(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = async.add(strategy, data, encodeSettings).await() + suspend inline fun add(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + async.add(data, encodeDefaults, serializersModule).await() + suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = + async.add(strategy, data, encodeDefaults, serializersModule).await() abstract val async: Async } @@ -408,13 +387,13 @@ expect class DocumentChange { } abstract class BaseDocumentSnapshot { - inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, decodeSettings: DecodeSettings = DecodeSettings()): T = decode(value = getEncoded(field, serverTimestampBehavior), decodeSettings) - fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, decodeSettings: DecodeSettings = DecodeSettings()): T = decode(strategy, getEncoded(field, serverTimestampBehavior), decodeSettings) + inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, serializersModule: SerializersModule = EmptySerializersModule()): T = decode(value = getEncoded(field, serverTimestampBehavior), serializersModule) + fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, serializersModule: SerializersModule = EmptySerializersModule()): T = decode(strategy, getEncoded(field, serverTimestampBehavior), serializersModule) abstract fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, decodeSettings: DecodeSettings = DecodeSettings()): T = decode(encodedData(serverTimestampBehavior), decodeSettings) - fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, decodeSettings: DecodeSettings = DecodeSettings()): T = decode(strategy, encodedData(serverTimestampBehavior), decodeSettings) + inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, serializersModule: SerializersModule = EmptySerializersModule()): T = decode(encodedData(serverTimestampBehavior), serializersModule) + fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, serializersModule: SerializersModule = EmptySerializersModule()): T = decode(strategy, encodedData(serverTimestampBehavior), serializersModule) abstract fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? } diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt index 269f8c4f1..1abf210cd 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt @@ -1,21 +1,24 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.EncodeSettings +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule import kotlin.jvm.JvmName //** Helper method to perform an update operation. */ @JvmName("performUpdateFields") fun encodeFieldAndValue( fieldsAndValues: Array>, - encodeSettings: EncodeSettings -) = encodeFieldAndValue(fieldsAndValues, encodeField = { it }, encodeValue = { encode(it, encodeSettings) }) + encodeDefaults: Boolean = true, + serializersModule: SerializersModule = EmptySerializersModule() +) = encodeFieldAndValue(fieldsAndValues, encodeField = { it }, encodeValue = { encode(it, encodeDefaults, serializersModule) }) /** Helper method to perform an update operation. */ @JvmName("performUpdateFieldPaths") fun encodeFieldAndValue( fieldsAndValues: Array>, - encodeSettings: EncodeSettings, -) = encodeFieldAndValue(fieldsAndValues, { it.encoded }, { encode(it, encodeSettings) }) + encodeDefaults: Boolean = true, + serializersModule: SerializersModule = EmptySerializersModule() +) = encodeFieldAndValue(fieldsAndValues, { it.encoded }, { encode(it, encodeDefaults, serializersModule) }) /** Helper method to perform an update operation in Android and JS. */ internal fun encodeFieldAndValue( 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 3944b23dc..2d6d745ac 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 @@ -541,7 +541,7 @@ class FirebaseFirestoreTest { prop1 = "prop1-updated", time = 123.0 ), - encodeSettings = EncodeSettings(shouldEncodeElementDefault = false), + encodeDefaults = false, ) batch.commit() @@ -568,7 +568,7 @@ class FirebaseFirestoreTest { prop1 = "prop1-set", time = 126.0 ), - encodeSettings = EncodeSettings(shouldEncodeElementDefault = false) + encodeDefaults = false ) batch.commit() @@ -731,7 +731,7 @@ class FirebaseFirestoreTest { fun encodeDocumentReference() = runTest { val doc = firestore.document("a/b") val item = TestDataWithDocumentReference("123", doc, doc) - val encoded = encodedAsMap(encode(item, EncodeSettings(shouldEncodeElementDefault = false))) + val encoded = encodedAsMap(encode(item, encodeDefaults = false)) assertEquals("123", encoded["uid"]) assertEquals(doc.nativeValue, encoded["reference"]) assertEquals(doc.nativeValue, encoded["optionalReference"]) @@ -740,7 +740,7 @@ class FirebaseFirestoreTest { @Test fun encodeNullDocumentReference() = runTest { val item = TestDataWithOptionalDocumentReference(null) - val encoded = encodedAsMap(encode(item, EncodeSettings(shouldEncodeElementDefault = false))) + val encoded = encodedAsMap(encode(item, encodeDefaults = false)) assertNull(encoded["optionalReference"]) } 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 7fe9a81bc..6a927adfa 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 @@ -12,6 +12,7 @@ import dev.gitlive.firebase.encode import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.SerializersModule import java.util.concurrent.TimeUnit actual val Firebase.functions @@ -44,8 +45,8 @@ actual class HttpsCallableResult constructor(val android: com.google.firebase.fu actual inline fun data() = decode(value = android.data) - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - decode(strategy, android.data, decodeSettings) + actual fun data(strategy: DeserializationStrategy, serializersModule: SerializersModule) = + decode(strategy, android.data, serializersModule) } actual typealias FirebaseFunctionsException = com.google.firebase.functions.FirebaseFunctionsException 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 9151cf1b9..50c62ac4d 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 @@ -7,6 +7,8 @@ package dev.gitlive.firebase.functions import dev.gitlive.firebase.* import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule expect class FirebaseFunctions { fun httpsCallable(name: String, timeout: Long? = null): HttpsCallableReference @@ -14,11 +16,18 @@ expect class FirebaseFunctions { } abstract class BaseHttpsCallableReference { - suspend inline operator fun invoke(data: T, encodeDefaults: Boolean) = invoke(data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend inline operator fun invoke(data: T, encodeSettings: EncodeSettings = EncodeSettings()): HttpsCallableResult = invoke(encode(data, encodeSettings)!!) + suspend inline operator fun invoke( + data: T, + encodeDefaults: Boolean = true, + serializersModule: SerializersModule = EmptySerializersModule() + ) = invoke(encode(data, encodeDefaults, serializersModule)!!) - suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): HttpsCallableResult = invoke(strategy, data, EncodeSettings(shouldEncodeElementDefault = encodeDefaults)) - suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): HttpsCallableResult = invoke(encode(strategy, data, encodeSettings)!!) + suspend operator fun invoke( + strategy: SerializationStrategy, + data: T, + encodeDefaults: Boolean = true, + serializersModule: SerializersModule = EmptySerializersModule() + ): HttpsCallableResult = invoke(strategy, data, encodeDefaults, serializersModule) abstract suspend fun invoke(encodedData: Any): HttpsCallableResult } @@ -28,7 +37,7 @@ expect class HttpsCallableReference : BaseHttpsCallableReference { expect class HttpsCallableResult { inline fun data(): T - fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings()): T + fun data(strategy: DeserializationStrategy, serializersModule: SerializersModule = EmptySerializersModule()): T } /** Returns the [FirebaseFunctions] instance of the default [FirebaseApp]. */ 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 a3616ff21..d48a82781 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 @@ -11,6 +11,8 @@ import dev.gitlive.firebase.* import kotlinx.coroutines.CompletableDeferred import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule import platform.Foundation.NSError actual val Firebase.functions @@ -50,8 +52,8 @@ actual class HttpsCallableResult constructor(val ios: FIRHTTPSCallableResult) { actual inline fun data() = decode(value = ios.data()) - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - decode(strategy, ios.data(), decodeSettings) + actual fun data(strategy: DeserializationStrategy, serializersModule: SerializersModule) = + decode(strategy, ios.data(), serializersModule) } actual class FirebaseFunctionsException(message: String, val code: FunctionsExceptionCode, val details: Any?) : FirebaseException(message) 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 4db0c0284..16fc22728 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 @@ -9,6 +9,7 @@ import dev.gitlive.firebase.functions.externals.* import kotlinx.coroutines.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.SerializersModule import kotlin.js.json import dev.gitlive.firebase.functions.externals.HttpsCallableResult as JsHttpsCallableResult @@ -47,8 +48,8 @@ actual class HttpsCallableResult constructor(val js: JsHttpsCallableResult) { actual inline fun data() = rethrow { decode(value = js.data) } - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - rethrow { decode(strategy, js.data, decodeSettings) } + actual fun data(strategy: DeserializationStrategy, serializersModule: SerializersModule) = + rethrow { decode(strategy, js.data, serializersModule) } } diff --git a/firebase-installations/package.json b/firebase-installations/package.json index ada558d69..3b8e2c9fc 100644 --- a/firebase-installations/package.json +++ b/firebase-installations/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-installations", - "version": "1.11.4", + "version": "1.11.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-installations.js", "scripts": { diff --git a/firebase-perf/package.json b/firebase-perf/package.json index 3df0d5a60..dcfc99cd5 100644 --- a/firebase-perf/package.json +++ b/firebase-perf/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-perf", - "version": "1.11.4", + "version": "1.11.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-perf.js", "scripts": { diff --git a/firebase-storage/package.json b/firebase-storage/package.json index c48b9b180..fbc5c0571 100644 --- a/firebase-storage/package.json +++ b/firebase-storage/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-storage", - "version": "1.11.4", + "version": "1.11.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-storage.js", "scripts": { From 83055074876a45174d0ace80c8046556c29db0e4 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 12 Jan 2024 20:19:17 +0100 Subject: [PATCH 08/27] Use builders instead Uses builders instead of named arguments to improve maintainability --- README.md | 8 - .../gitlive/firebase/EncodeDecodeSettings.kt | 27 +- .../kotlin/dev/gitlive/firebase/decoders.kt | 10 +- .../kotlin/dev/gitlive/firebase/encoders.kt | 23 +- .../dev/gitlive/firebase/EncodersTest.kt | 19 +- firebase-crashlytics/package.json | 2 +- .../dev/gitlive/firebase/database/database.kt | 17 +- .../dev/gitlive/firebase/database/database.kt | 58 ++-- .../dev/gitlive/firebase/database/database.kt | 27 +- .../dev/gitlive/firebase/database/database.kt | 15 +- .../gitlive/firebase/firestore/encoders.kt | 7 +- .../gitlive/firebase/firestore/firestore.kt | 263 +++++++++++++----- .../dev/gitlive/firebase/firestore/helpers.kt | 13 +- .../gitlive/firebase/firestore/firestore.kt | 23 +- .../gitlive/firebase/functions/functions.kt | 5 +- .../gitlive/firebase/functions/functions.kt | 25 +- .../gitlive/firebase/functions/functions.kt | 6 +- .../gitlive/firebase/functions/functions.kt | 5 +- firebase-installations/package.json | 2 +- firebase-perf/package.json | 2 +- firebase-storage/package.json | 2 +- 21 files changed, 353 insertions(+), 206 deletions(-) diff --git a/README.md b/README.md index 6c6095fc7..dff3de3e4 100644 --- a/README.md +++ b/README.md @@ -210,14 +210,6 @@ citiesRef.where { } ``` -

Named arguments

- -To improve readability functions such as the Cloud Firestore data encoding/decoding use named arguments: - -```kotlin -documentRef.set(data, encodeDefaults = false, serializersModule = customSerializerModule) -``` -

Operator overloading

In cases where it makes sense, such as Firebase Functions HTTPS Callable, operator overloading is used: 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 6a69db5a0..2ed6d5b3f 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt @@ -19,15 +19,30 @@ sealed class EncodeDecodeSettings { * @property shouldEncodeElementDefault 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( - val shouldEncodeElementDefault: Boolean = true, - override val serializersModule: SerializersModule = EmptySerializersModule(), -) : EncodeDecodeSettings() +data class EncodeSettings internal constructor( + val shouldEncodeElementDefault: Boolean, + override val serializersModule: SerializersModule, +) : EncodeDecodeSettings() { + class Builder { + var shouldEncodeElementDefault: Boolean = true + var serializersModule: SerializersModule = EmptySerializersModule() + + @PublishedApi + internal fun build() = EncodeSettings(shouldEncodeElementDefault, serializersModule) + } +} /** * [EncodeDecodeSettings] used when decoding an object * @param serializersModule the [SerializersModule] to use for deserialization. This allows for polymorphic serialization on runtime */ -data class DecodeSettings( +data class DecodeSettings internal constructor( override val serializersModule: SerializersModule = EmptySerializersModule(), -) : EncodeDecodeSettings() +) : EncodeDecodeSettings() { + + class Builder { + var serializersModule: SerializersModule = EmptySerializersModule() + + internal fun build() = DecodeSettings(serializersModule) + } +} diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt index 2654d2c1d..226ec511e 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt @@ -15,13 +15,15 @@ import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer -inline fun decode(value: Any?, serializersModule: SerializersModule = EmptySerializersModule()): T { +inline fun decode(value: Any?): T = decode(value) {} +inline fun decode(value: Any?, noinline buildSettings: DecodeSettings.Builder.() -> Unit): T { val strategy = serializer() - return decode(strategy as DeserializationStrategy, value, serializersModule) + return decode(strategy as DeserializationStrategy, value, buildSettings) } -fun decode(strategy: DeserializationStrategy, value: Any?, serializersModule: SerializersModule = EmptySerializersModule()): T { +fun decode(strategy: DeserializationStrategy, value: Any?): T = decode(strategy, value) {} +fun decode(strategy: DeserializationStrategy, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T { require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" } - return FirebaseDecoder(value, DecodeSettings(serializersModule)).decodeSerializableValue(strategy) + return FirebaseDecoder(value, DecodeSettings.Builder().apply(buildSettings).build()).decodeSerializableValue(strategy) } expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder expect fun getPolymorphicType(value: Any?, discriminator: String): String diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 7c3e3c9ed..1d3d08dd2 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -7,17 +7,22 @@ package dev.gitlive.firebase import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule -fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean, serializersModule: SerializersModule = EmptySerializersModule()): Any? = encode(strategy, value, EncodeSettings(shouldEncodeElementDefault, serializersModule)) +@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(strategy, value) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }")) +fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = encode(strategy, value) { + this.shouldEncodeElementDefault = shouldEncodeElementDefault +} -fun encode(strategy: SerializationStrategy, value: T, settings: EncodeSettings): Any? = - FirebaseEncoder(settings).apply { encodeSerializableValue(strategy, value) }.value +fun encode(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = + FirebaseEncoder(EncodeSettings.Builder().apply(buildSettings).build()).apply { encodeSerializableValue(strategy, value) }.value -inline fun encode(value: T, shouldEncodeElementDefault: Boolean, serializersModule: SerializersModule = EmptySerializersModule()): Any? = encode(value, EncodeSettings(shouldEncodeElementDefault, serializersModule)) -inline fun encode(value: T, settings: EncodeSettings): Any? = value?.let { - FirebaseEncoder(settings).apply { +@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(value) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }")) +inline fun encode(value: T, shouldEncodeElementDefault: Boolean): Any? = encode(value) { + this.shouldEncodeElementDefault = shouldEncodeElementDefault +} +inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = value?.let { + FirebaseEncoder(EncodeSettings.Builder().apply(buildSettings).build()).apply { if (it is ValueWithSerializer<*> && it.value is T) { @Suppress("UNCHECKED_CAST") (it as ValueWithSerializer).let { @@ -43,7 +48,9 @@ class FirebaseEncoder( internal val settings: EncodeSettings ) : Encoder { - constructor(shouldEncodeElementDefault: Boolean, serializersModule: SerializersModule = EmptySerializersModule()) : this(EncodeSettings(shouldEncodeElementDefault, serializersModule)) + constructor(shouldEncodeElementDefault: Boolean) : this( + EncodeSettings.Builder().apply { this.shouldEncodeElementDefault = shouldEncodeElementDefault }.build() + ) var value: Any? = null diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index c43b5ce39..f1feedf5b 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -141,11 +141,17 @@ class EncodersTest { polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) } val abstractClass: AbstractClass = ImplementedClass("value", true) - val encoded = encode(AbstractClass.serializer(), abstractClass, EncodeSettings(true, module)) + val encoded = + encode(AbstractClass.serializer(), abstractClass) { + shouldEncodeElementDefault = true + serializersModule = module + } nativeAssertEquals(nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true), encoded) - val decoded = decode(AbstractClass.serializer(), encoded, module) + val decoded = decode(AbstractClass.serializer(), encoded) { + serializersModule = module + } assertEquals(abstractClass, decoded) } @@ -158,7 +164,10 @@ class EncodersTest { val sealedClass: SealedClass = SealedClass.Test("value") val abstractClass: AbstractClass = ImplementedClass("value", true) val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) - val encoded = encode(NestedClass.serializer(), nestedClass, EncodeSettings(true, module)) + val encoded = encode(NestedClass.serializer(), nestedClass) { + shouldEncodeElementDefault = true + serializersModule = module + } val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") val abstractEncoded = nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true) @@ -174,7 +183,9 @@ class EncodersTest { encoded ) - val decoded = decode(NestedClass.serializer(), encoded, module) + val decoded = decode(NestedClass.serializer(), encoded) { + serializersModule = module + } assertEquals(nestedClass, decoded) } } diff --git a/firebase-crashlytics/package.json b/firebase-crashlytics/package.json index 2e03d9beb..360f26f85 100644 --- a/firebase-crashlytics/package.json +++ b/firebase-crashlytics/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-crashlytics", - "version": "1.11.0", + "version": "1.11.4", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-crashlytics.js", "scripts": { 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 ca7f2508d..517142050 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 @@ -16,7 +16,6 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer -import kotlinx.serialization.modules.SerializersModule import java.util.* import kotlin.time.Duration.Companion.seconds @@ -184,8 +183,8 @@ actual class DatabaseReference internal constructor( .run { Unit } @Suppress("UNCHECKED_CAST") - override suspend fun updateChildren(update: Map, encodeDefaults: Boolean, serializersModule: SerializersModule) = - android.updateChildren(encode(update, encodeDefaults, serializersModule) as Map) + override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) = + android.updateChildren(encode(update, buildSettings) as Map) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @@ -193,13 +192,13 @@ actual class DatabaseReference internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun runTransaction(strategy: KSerializer, serializersModule: SerializersModule, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() android.runTransaction(object : Transaction.Handler { override fun doTransaction(currentData: MutableData): Transaction.Result { currentData.value = currentData.value?.let { - transactionUpdate(decode(strategy, it, serializersModule)) + transactionUpdate(decode(strategy, it, buildSettings)) } return Transaction.success(currentData) } @@ -237,8 +236,8 @@ actual class DataSnapshot internal constructor( actual inline fun value() = decode(value = android.value) - actual fun value(strategy: DeserializationStrategy, serializersModule: SerializersModule) = - decode(strategy, android.value, serializersModule) + actual fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + decode(strategy, android.value, buildSettings) actual fun child(path: String) = DataSnapshot(android.child(path), persistenceEnabled) actual val hasChildren get() = android.hasChildren() @@ -263,8 +262,8 @@ actual class OnDisconnect internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - override suspend fun updateChildren(update: Map, encodeDefaults: Boolean, serializersModule: SerializersModule) = - android.updateChildren(update.mapValues { (_, it) -> encode(it, encodeDefaults, serializersModule) }) + override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) = + android.updateChildren(update.mapValues { (_, it) -> encode(it, buildSettings) }) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } } diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index dcde429a4..6be3699ff 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 @@ -4,19 +4,16 @@ package dev.gitlive.firebase.database +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeSettings 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.database.ChildEvent.Type.* import dev.gitlive.firebase.encode import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.EmptySerializersModule -import kotlinx.serialization.modules.SerializersModule /** Returns the [FirebaseDatabase] instance of the default [FirebaseApp]. */ expect val Firebase.database: FirebaseDatabase @@ -73,13 +70,29 @@ expect open class Query internal constructor(nativeQuery: NativeQuery) { } abstract class BaseDatabaseReference internal constructor(nativeQuery: NativeQuery) : Query(nativeQuery) { - suspend inline fun setValue(value: T?, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - setValueEncoded(encode(value, encodeDefaults, serializersModule)) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - setValueEncoded(encode(strategy, value, encodeDefaults, serializersModule)) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { shouldEncodeElementDefault = encodeDefaults }")) + suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = + setValue(value) { + shouldEncodeElementDefault = encodeDefaults + } + suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setValueEncoded(encode(value, buildSettings)) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { shouldEncodeElementDefault = encodeDefaults }")) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = + setValue(strategy, value) { + shouldEncodeElementDefault = encodeDefaults + } + suspend fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValueEncoded(encode(strategy, value, buildSettings)) abstract suspend fun setValueEncoded(encodedValue: Any?) - abstract suspend fun updateChildren(update: Map, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { shouldEncodeElementDefault = encodeDefaults }")) + suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update) { + shouldEncodeElementDefault = encodeDefaults + } + abstract suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) } expect class DatabaseReference : BaseDatabaseReference { @@ -90,7 +103,7 @@ expect class DatabaseReference : BaseDatabaseReference { suspend fun removeValue() - suspend fun runTransaction(strategy: KSerializer, serializersModule: SerializersModule = EmptySerializersModule(), transactionUpdate: (currentData: T) -> T): DataSnapshot + suspend fun runTransaction(strategy: KSerializer, buildSettings: DecodeSettings.Builder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot } expect class DataSnapshot { @@ -99,7 +112,7 @@ expect class DataSnapshot { val ref: DatabaseReference val value: Any? inline fun value(): T - fun value(strategy: DeserializationStrategy, serializersModule: SerializersModule = EmptySerializersModule()): T + fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T fun child(path: String): DataSnapshot val hasChildren: Boolean val children: Iterable @@ -108,13 +121,22 @@ expect class DataSnapshot { expect class DatabaseException(message: String?, cause: Throwable?) : RuntimeException abstract class BaseOnDisconnect internal constructor() { - suspend inline fun setValue(value: T?, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - setValue(encode(value, encodeDefaults, serializersModule)) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - setValue(encode(strategy, value, encodeDefaults, serializersModule)) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { shouldEncodeElementDefault = encodeDefaults }")) + suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = + setValue(value) { shouldEncodeElementDefault = encodeDefaults } + suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setValue(encode(value, buildSettings)) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { shouldEncodeElementDefault = encodeDefaults }")) + suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = + setValue(strategy, value) { shouldEncodeElementDefault = encodeDefaults } + suspend fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValue(encode(strategy, value, buildSettings)) abstract suspend fun setValue(encodedValue: Any?) - abstract suspend fun updateChildren(update: Map, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) + abstract suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { shouldEncodeElementDefault = encodeDefaults }")) + suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update) { + shouldEncodeElementDefault = encodeDefaults + } } expect class OnDisconnect : BaseOnDisconnect { 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 8884bc59b..a6a014aa1 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 @@ -20,8 +20,6 @@ import kotlinx.coroutines.selects.select import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.EmptySerializersModule -import kotlinx.serialization.modules.SerializersModule import platform.Foundation.NSError import platform.Foundation.allObjects import kotlin.collections.component1 @@ -152,24 +150,21 @@ actual class DatabaseReference internal constructor( ios.await(persistenceEnabled) { setValue(encodedValue, it) } } - override suspend fun updateChildren( - update: Map, - encodeDefaults: Boolean, - serializersModule: SerializersModule - ) { - ios.await(persistenceEnabled) { updateChildValues(encode(update, encodeDefaults, serializersModule) as Map, it) } + @Suppress("UNCHECKED_CAST") + override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) { + ios.await(persistenceEnabled) { updateChildValues(encode(update, buildSettings) as Map, it) } } actual suspend fun removeValue() { ios.await(persistenceEnabled) { removeValueWithCompletionBlock(it) } } - actual suspend fun runTransaction(strategy: KSerializer, serializersModule: SerializersModule, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() ios.runTransactionBlock( block = { firMutableData -> firMutableData?.value = firMutableData?.value?.let { - transactionUpdate(decode(strategy, it, serializersModule)) + transactionUpdate(decode(strategy, it, buildSettings)) } FIRTransactionResult.successWithValue(firMutableData!!) }, @@ -203,8 +198,8 @@ actual class DataSnapshot internal constructor( actual inline fun value() = decode(value = ios.value) - actual fun value(strategy: DeserializationStrategy, serializersModule: SerializersModule) = - decode(strategy, ios.value, serializersModule) + actual fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + decode(strategy, ios.value, buildSettings) actual fun child(path: String) = DataSnapshot(ios.childSnapshotForPath(path), persistenceEnabled) actual val hasChildren get() = ios.hasChildren() @@ -228,12 +223,8 @@ actual class OnDisconnect internal constructor( } @Suppress("UNCHECKED_CAST") - override suspend fun updateChildren( - update: Map, - encodeDefaults: Boolean, - serializersModule: SerializersModule - ) { - ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it, encodeDefaults, serializersModule) } as Map, it) } + override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) { + ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it, buildSettings) } as Map, it) } } } diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index ef6119487..a1ad24b04 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 @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.produceIn import kotlinx.coroutines.selects.select import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer -import kotlinx.serialization.modules.SerializersModule import kotlin.js.Promise import kotlin.js.json import dev.gitlive.firebase.database.externals.DataSnapshot as JsDataSnapshot @@ -153,11 +152,11 @@ actual class DatabaseReference internal constructor( set(js, encodedValue).awaitWhileOnline(database) } - override suspend fun updateChildren(update: Map, encodeDefaults: Boolean, serializersModule: SerializersModule) = - rethrow { update(js, encode(update, encodeDefaults, serializersModule) ?: json()).awaitWhileOnline(database) } + override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) = + rethrow { update(js, encode(update, buildSettings) ?: json()).awaitWhileOnline(database) } - actual suspend fun runTransaction(strategy: KSerializer, serializersModule: SerializersModule, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { return DataSnapshot(jsRunTransaction(js, transactionUpdate).awaitWhileOnline(database).snapshot, database) } } @@ -174,8 +173,8 @@ actual class DataSnapshot internal constructor( actual inline fun value() = rethrow { decode(value = js.`val`()) } - actual fun value(strategy: DeserializationStrategy, serializersModule: SerializersModule) = - rethrow { decode(strategy, js.`val`(), serializersModule) } + actual fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + rethrow { decode(strategy, js.`val`(), buildSettings) } actual val exists get() = rethrow { js.exists() } actual val key get() = rethrow { js.key } @@ -202,8 +201,8 @@ actual class OnDisconnect internal constructor( override suspend fun setValue(encodedValue: Any?) = rethrow { js.set(encodedValue).awaitWhileOnline(database) } - override suspend fun updateChildren(update: Map, encodeDefaults: Boolean, serializersModule: SerializersModule) = - rethrow { js.update(encode(update, encodeDefaults, serializersModule) ?: json()).awaitWhileOnline(database) } + override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) = + rethrow { js.update(encode(update, buildSettings) ?: json()).awaitWhileOnline(database) } } 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 6b506ebc9..04a3f32cf 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 @@ -1,16 +1,15 @@ package dev.gitlive.firebase.firestore -import kotlinx.serialization.modules.EmptySerializersModule -import kotlinx.serialization.modules.SerializersModule +import dev.gitlive.firebase.EncodeSettings /** @return whether value is special and shouldn't be encoded/decoded. */ @PublishedApi internal expect fun isSpecialValue(value: Any): Boolean @PublishedApi -internal inline fun encode(value: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = +internal inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit) = if (value?.let(::isSpecialValue) == true) { value } else { - dev.gitlive.firebase.encode(value, encodeDefaults, serializersModule) + dev.gitlive.firebase.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 7dcc63e15..939ee1c2f 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -10,8 +10,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.EmptySerializersModule -import kotlinx.serialization.modules.SerializersModule import kotlin.jvm.JvmName /** Returns the [FirebaseFirestore] instance of the default [FirebaseApp]. */ @@ -45,24 +43,61 @@ sealed class SetOptions { abstract class BaseTransaction { - fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false): BaseTransaction = - setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean = false): BaseTransaction = set(documentRef, data, merge) { + shouldEncodeElementDefault = encodeDefaults + } + fun set(documentRef: DocumentReference, data: Any, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, *mergeFields) { + shouldEncodeElementDefault = encodeDefaults + } + fun set(documentRef: DocumentReference, data: Any, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, *mergeFieldPaths) { + shouldEncodeElementDefault = encodeDefaults + } + fun set(documentRef: DocumentReference, data: Any, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, merge) { + shouldEncodeElementDefault = encodeDefaults + } + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, *mergeFields) { + shouldEncodeElementDefault = encodeDefaults + } + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, *mergeFieldPaths) { + shouldEncodeElementDefault = encodeDefaults + } + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) protected abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseTransaction - fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { shouldEncodeElementDefault = encodeDefaults }")) + fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = update(documentRef, data) { + shouldEncodeElementDefault = encodeDefaults + } + fun update(documentRef: DocumentReference, data: Any, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encode(data, buildSettings)!!) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data) { + shouldEncodeElementDefault = encodeDefaults + } + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encode(strategy, data, buildSettings)!!) @JvmName("updateFields") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @JvmName("updateFieldPaths") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) protected abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction protected abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction @@ -176,31 +211,68 @@ internal val Any.safeValue: Any get() = when (this) { abstract class BaseWriteBatch { - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = - setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = - setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) - inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = - setEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, data, merge) { + shouldEncodeElementDefault = 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) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, *mergeFields) { + shouldEncodeElementDefault = encodeDefaults + } + inline fun set(documentRef: DocumentReference, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, *mergeFieldPaths) { + shouldEncodeElementDefault = encodeDefaults + } + inline fun set(documentRef: DocumentReference, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, merge) { + shouldEncodeElementDefault = encodeDefaults + } + 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) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = - setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = - setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = - setEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, *mergeFields){ + shouldEncodeElementDefault = encodeDefaults + } + 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())) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, *mergeFieldPaths) { + shouldEncodeElementDefault = encodeDefaults + } + 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())) abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseWriteBatch - inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - updateEncoded(documentRef, encode(data, encodeDefaults, serializersModule)!!) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - updateEncoded(documentRef, encode(strategy, data, encodeDefaults, serializersModule)!!) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { shouldEncodeElementDefault = encodeDefaults }")) + inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = update(documentRef, data){ + shouldEncodeElementDefault = encodeDefaults + } + inline fun update(documentRef: DocumentReference, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + updateEncoded(documentRef, encode(data, buildSettings)!!) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data) { + shouldEncodeElementDefault = encodeDefaults + } + fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + updateEncoded(documentRef, encode(strategy, data, buildSettings)!!) @JvmName("updateField") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @JvmName("updateFieldPath") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) + fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch @@ -226,26 +298,26 @@ expect class NativeDocumentReference abstract class BaseDocumentReference { abstract class Async { - inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = setEncoded(encode(data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = setEncoded(encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) - inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = setEncoded(encode(data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = setEncoded( - encode(strategy, data, encodeDefaults, serializersModule)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String)= setEncoded( - encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = setEncoded( - encode(strategy, data, encodeDefaults, serializersModule)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( + encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) + fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {})= setEncoded( + encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( + encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) abstract fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred - inline fun update(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncoded(encode(data, encodeDefaults, serializersModule)!!) - fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = update(encode(strategy, data, encodeDefaults, serializersModule)) + inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(encode(data, buildSettings)!!) + fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = update(encode(strategy, data, buildSettings)) @JvmName("updateFields") - fun update(vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) + fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @JvmName("updateFieldPaths") - fun update(vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, encodeDefaults, serializersModule).orEmpty()) + fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) abstract fun updateEncoded(encodedData: Any): Deferred protected abstract fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred @@ -256,37 +328,69 @@ abstract class BaseDocumentReference { abstract val async: Async - suspend inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = - async.set(data, encodeDefaults, serializersModule, merge).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(data, merge) { + shouldEncodeElementDefault = encodeDefaults + } + suspend inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.set(data, merge, buildSettings).await() - suspend inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = - async.set(data, encodeDefaults, serializersModule, mergeFields = mergeFields).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(data, *mergeFields) { + shouldEncodeElementDefault = encodeDefaults + } + suspend inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.set(data, mergeFields = mergeFields, buildSettings = buildSettings).await() - suspend inline fun set(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = - async.set(data, encodeDefaults, serializersModule, mergeFieldPaths = mergeFieldPaths).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(data, *mergeFieldPaths) { + shouldEncodeElementDefault = encodeDefaults + } + suspend inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.set(data, mergeFieldPaths = mergeFieldPaths, buildSettings = buildSettings).await() - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), merge: Boolean = false) = - async.set(strategy, data, encodeDefaults, serializersModule, merge).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(strategy, data, merge) { + shouldEncodeElementDefault = encodeDefaults + } + suspend fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.set(strategy, data, merge, buildSettings).await() - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFields: String) = - async.set(strategy, data, encodeDefaults, serializersModule, mergeFields = mergeFields).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(strategy, data, *mergeFields) { + shouldEncodeElementDefault = encodeDefaults + } + suspend fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.set(strategy, data, mergeFields = mergeFields, buildSettings).await() - suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule(), vararg mergeFieldPaths: FieldPath) = - async.set(strategy, data, encodeDefaults, serializersModule, mergeFieldPaths = mergeFieldPaths).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(strategy, data, *mergeFieldPaths) { + shouldEncodeElementDefault = encodeDefaults + } + suspend fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.set(strategy, data, mergeFieldPaths = mergeFieldPaths, buildSettings).await() - suspend inline fun update(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - async.update(data, encodeDefaults, serializersModule).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(data) { shouldEncodeElementDefault = encodeDefaults }")) + suspend inline fun update(data: T, encodeDefaults: Boolean) = update(data) { + shouldEncodeElementDefault = encodeDefaults + } + suspend inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.update(data, buildSettings).await() - suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - async.update(strategy, data, encodeDefaults, serializersModule).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(strategy, data) { + shouldEncodeElementDefault = encodeDefaults + } + suspend fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.update(strategy, data, buildSettings).await() @JvmName("updateFields") - suspend fun update(vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - async.update(fieldsAndValues = fieldsAndValues, encodeDefaults, serializersModule).await() + suspend fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.update(fieldsAndValues = fieldsAndValues, buildSettings).await() @JvmName("updateFieldPaths") - suspend fun update(vararg fieldsAndValues: Pair, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - async.update(fieldsAndValues = fieldsAndValues, encodeDefaults, serializersModule).await() + suspend fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + async.update(fieldsAndValues = fieldsAndValues, buildSettings).await() suspend fun delete() = async.delete().await() @@ -311,20 +415,27 @@ abstract class BaseCollectionReference(nativeQuery: NativeQuery) : Query(nativeQ @Suppress("DeferredIsResult") abstract class Async { - inline fun add(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = addEncoded( - encode(data, encodeDefaults, serializersModule)!! + inline fun add(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( + encode(data, buildSettings)!! ) - fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = addEncoded( - encode(strategy, data, encodeDefaults, serializersModule)!! + fun add(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( + encode(strategy, data, buildSettings)!! ) abstract fun addEncoded(data: Any): Deferred } - suspend inline fun add(data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - async.add(data, encodeDefaults, serializersModule).await() - suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean = true, serializersModule: SerializersModule = EmptySerializersModule()) = - async.add(strategy, data, encodeDefaults, serializersModule).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(data) { shouldEncodeElementDefault = encodeDefaults }")) + suspend inline fun add(data: T, encodeDefaults: Boolean) = add(data) { + shouldEncodeElementDefault = encodeDefaults + } + suspend inline fun add(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = async.add(data, buildSettings).await() + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = add(strategy, data) { + shouldEncodeElementDefault = encodeDefaults + } + suspend fun add(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = async.add(strategy, data, buildSettings).await() abstract val async: Async } @@ -387,13 +498,13 @@ expect class DocumentChange { } abstract class BaseDocumentSnapshot { - inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, serializersModule: SerializersModule = EmptySerializersModule()): T = decode(value = getEncoded(field, serverTimestampBehavior), serializersModule) - fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, serializersModule: SerializersModule = EmptySerializersModule()): T = decode(strategy, getEncoded(field, serverTimestampBehavior), serializersModule) + inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, noinline buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(value = getEncoded(field, serverTimestampBehavior), buildSettings) + fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(field, serverTimestampBehavior), buildSettings) abstract fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, serializersModule: SerializersModule = EmptySerializersModule()): T = decode(encodedData(serverTimestampBehavior), serializersModule) - fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, serializersModule: SerializersModule = EmptySerializersModule()): T = decode(strategy, encodedData(serverTimestampBehavior), serializersModule) + inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, noinline buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(encodedData(serverTimestampBehavior), buildSettings) + fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, encodedData(serverTimestampBehavior), buildSettings) abstract fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? } diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt index 1abf210cd..bac8e2e6a 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt @@ -1,24 +1,21 @@ package dev.gitlive.firebase.firestore -import kotlinx.serialization.modules.EmptySerializersModule -import kotlinx.serialization.modules.SerializersModule +import dev.gitlive.firebase.EncodeSettings import kotlin.jvm.JvmName //** Helper method to perform an update operation. */ @JvmName("performUpdateFields") fun encodeFieldAndValue( fieldsAndValues: Array>, - encodeDefaults: Boolean = true, - serializersModule: SerializersModule = EmptySerializersModule() -) = encodeFieldAndValue(fieldsAndValues, encodeField = { it }, encodeValue = { encode(it, encodeDefaults, serializersModule) }) + buildSettings: EncodeSettings.Builder.() -> Unit, +) = encodeFieldAndValue(fieldsAndValues, encodeField = { it }, encodeValue = { encode(it, buildSettings) }) /** Helper method to perform an update operation. */ @JvmName("performUpdateFieldPaths") fun encodeFieldAndValue( fieldsAndValues: Array>, - encodeDefaults: Boolean = true, - serializersModule: SerializersModule = EmptySerializersModule() -) = encodeFieldAndValue(fieldsAndValues, { it.encoded }, { encode(it, encodeDefaults, serializersModule) }) + buildSettings: EncodeSettings.Builder.() -> Unit, +) = encodeFieldAndValue(fieldsAndValues, { it.encoded }, { encode(it, buildSettings) }) /** Helper method to perform an update operation in Android and JS. */ internal fun encodeFieldAndValue( 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 2d6d745ac..2fe196e66 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 @@ -4,7 +4,6 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.EncodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps @@ -541,8 +540,9 @@ class FirebaseFirestoreTest { prop1 = "prop1-updated", time = 123.0 ), - encodeDefaults = false, - ) + ) { + shouldEncodeElementDefault = false + } batch.commit() assertEquals("prop1-updated", doc.get().data(FirestoreTest.serializer()).prop1) @@ -568,8 +568,9 @@ class FirebaseFirestoreTest { prop1 = "prop1-set", time = 126.0 ), - encodeDefaults = false - ) + ) { + shouldEncodeElementDefault = false + } batch.commit() assertEquals(126.0, doc.get().get("time") as Double?) @@ -731,7 +732,11 @@ class FirebaseFirestoreTest { fun encodeDocumentReference() = runTest { val doc = firestore.document("a/b") val item = TestDataWithDocumentReference("123", doc, doc) - val encoded = encodedAsMap(encode(item, encodeDefaults = false)) + val encoded = encodedAsMap( + encode(item) { + shouldEncodeElementDefault = false + } + ) assertEquals("123", encoded["uid"]) assertEquals(doc.nativeValue, encoded["reference"]) assertEquals(doc.nativeValue, encoded["optionalReference"]) @@ -740,7 +745,11 @@ class FirebaseFirestoreTest { @Test fun encodeNullDocumentReference() = runTest { val item = TestDataWithOptionalDocumentReference(null) - val encoded = encodedAsMap(encode(item, encodeDefaults = false)) + val encoded = encodedAsMap( + encode(item) { + shouldEncodeElementDefault = false + } + ) assertNull(encoded["optionalReference"]) } 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 6a927adfa..0c525b1ad 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 @@ -12,7 +12,6 @@ import dev.gitlive.firebase.encode import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.SerializersModule import java.util.concurrent.TimeUnit actual val Firebase.functions @@ -45,8 +44,8 @@ actual class HttpsCallableResult constructor(val android: com.google.firebase.fu actual inline fun data() = decode(value = android.data) - actual fun data(strategy: DeserializationStrategy, serializersModule: SerializersModule) = - decode(strategy, android.data, serializersModule) + actual fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + decode(strategy, android.data, buildSettings) } actual typealias FirebaseFunctionsException = com.google.firebase.functions.FirebaseFunctionsException 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 50c62ac4d..5544fd30d 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 @@ -7,8 +7,6 @@ package dev.gitlive.firebase.functions import dev.gitlive.firebase.* import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.EmptySerializersModule -import kotlinx.serialization.modules.SerializersModule expect class FirebaseFunctions { fun httpsCallable(name: String, timeout: Long? = null): HttpsCallableReference @@ -16,18 +14,17 @@ expect class FirebaseFunctions { } abstract class BaseHttpsCallableReference { - suspend inline operator fun invoke( - data: T, - encodeDefaults: Boolean = true, - serializersModule: SerializersModule = EmptySerializersModule() - ) = invoke(encode(data, encodeDefaults, serializersModule)!!) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(data) { shouldEncodeElementDefault = encodeDefaults }")) + suspend inline operator fun invoke(data: T, encodeDefaults: Boolean) = invoke(data) { + shouldEncodeElementDefault = encodeDefaults + } + suspend inline operator fun invoke(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = invoke(encode(data, buildSettings)!!) - suspend operator fun invoke( - strategy: SerializationStrategy, - data: T, - encodeDefaults: Boolean = true, - serializersModule: SerializersModule = EmptySerializersModule() - ): HttpsCallableResult = invoke(strategy, data, encodeDefaults, serializersModule) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): HttpsCallableResult = invoke(strategy, data) { + shouldEncodeElementDefault = encodeDefaults + } + suspend operator fun invoke(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = invoke(encode(strategy, data, buildSettings)!!) abstract suspend fun invoke(encodedData: Any): HttpsCallableResult } @@ -37,7 +34,7 @@ expect class HttpsCallableReference : BaseHttpsCallableReference { expect class HttpsCallableResult { inline fun data(): T - fun data(strategy: DeserializationStrategy, serializersModule: SerializersModule = EmptySerializersModule()): T + fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T } /** Returns the [FirebaseFunctions] instance of the default [FirebaseApp]. */ 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 d48a82781..102696fc6 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 @@ -11,8 +11,6 @@ import dev.gitlive.firebase.* import kotlinx.coroutines.CompletableDeferred import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.EmptySerializersModule -import kotlinx.serialization.modules.SerializersModule import platform.Foundation.NSError actual val Firebase.functions @@ -52,8 +50,8 @@ actual class HttpsCallableResult constructor(val ios: FIRHTTPSCallableResult) { actual inline fun data() = decode(value = ios.data()) - actual fun data(strategy: DeserializationStrategy, serializersModule: SerializersModule) = - decode(strategy, ios.data(), serializersModule) + actual fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + decode(strategy, ios.data(), buildSettings) } actual class FirebaseFunctionsException(message: String, val code: FunctionsExceptionCode, val details: Any?) : FirebaseException(message) 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 16fc22728..64ea488e0 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 @@ -9,7 +9,6 @@ import dev.gitlive.firebase.functions.externals.* import kotlinx.coroutines.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.SerializersModule import kotlin.js.json import dev.gitlive.firebase.functions.externals.HttpsCallableResult as JsHttpsCallableResult @@ -48,8 +47,8 @@ actual class HttpsCallableResult constructor(val js: JsHttpsCallableResult) { actual inline fun data() = rethrow { decode(value = js.data) } - actual fun data(strategy: DeserializationStrategy, serializersModule: SerializersModule) = - rethrow { decode(strategy, js.data, serializersModule) } + actual fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + rethrow { decode(strategy, js.data, buildSettings) } } diff --git a/firebase-installations/package.json b/firebase-installations/package.json index 3b8e2c9fc..ada558d69 100644 --- a/firebase-installations/package.json +++ b/firebase-installations/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-installations", - "version": "1.11.0", + "version": "1.11.4", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-installations.js", "scripts": { diff --git a/firebase-perf/package.json b/firebase-perf/package.json index dcfc99cd5..3df0d5a60 100644 --- a/firebase-perf/package.json +++ b/firebase-perf/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-perf", - "version": "1.11.0", + "version": "1.11.4", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-perf.js", "scripts": { diff --git a/firebase-storage/package.json b/firebase-storage/package.json index fbc5c0571..c48b9b180 100644 --- a/firebase-storage/package.json +++ b/firebase-storage/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-storage", - "version": "1.11.0", + "version": "1.11.4", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-storage.js", "scripts": { From 2b5afa479bb8be4f9f2dea7e967b2a540726c88f Mon Sep 17 00:00:00 2001 From: Andrew Reed Date: Mon, 15 Jan 2024 08:44:09 +0000 Subject: [PATCH 09/27] Release/1.11.1 (#461) * bumped the version * bumped the version --- README.md | 20 ++++++++++---------- firebase-app/package.json | 4 ++-- firebase-auth/package.json | 4 ++-- firebase-common/package.json | 2 +- firebase-config/package.json | 4 ++-- firebase-crashlytics/package.json | 2 +- firebase-database/package.json | 4 ++-- firebase-firestore/package.json | 4 ++-- firebase-functions/package.json | 4 ++-- firebase-installations/package.json | 2 +- firebase-perf/package.json | 2 +- firebase-storage/package.json | 2 +- gradle.properties | 22 +++++++++++----------- 13 files changed, 38 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index dff3de3e4..d8ced5365 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,16 @@ The following libraries are available for the various Firebase products. | Service or Product | Gradle Dependency | API Coverage | |---------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [Authentication](https://firebase.google.com/docs/auth) | [`dev.gitlive:firebase-auth:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/1.11.0/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) | -| [Realtime Database](https://firebase.google.com/docs/database) | [`dev.gitlive:firebase-database:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-database/1.11.0/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) | -| [Cloud Firestore](https://firebase.google.com/docs/firestore) | [`dev.gitlive:firebase-firestore:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/1.11.0/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) | -| [Cloud Functions](https://firebase.google.com/docs/functions) | [`dev.gitlive:firebase-functions:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-functions/1.11.0/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt) | -| [Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) | [`dev.gitlive:firebase-messaging:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-messaging/1.11.0/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | -| [Cloud Storage](https://firebase.google.com/docs/storage) | [`dev.gitlive:firebase-storage:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-storage/1.11.0/pom) | [![40%](https://img.shields.io/badge/-40%25-orange?style=flat-square)](/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt) | -| [Installations](https://firebase.google.com/docs/projects/manage-installations) | [`dev.gitlive:firebase-installations:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-installations/1.11.0/pom) | [![90%](https://img.shields.io/badge/-90%25-green?style=flat-square)](/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt) | -| [Remote Config](https://firebase.google.com/docs/remote-config) | [`dev.gitlive:firebase-config:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-config/1.11.0/pom) | [![20%](https://img.shields.io/badge/-20%25-orange?style=flat-square)](/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt) | -| [Performance](https://firebase.google.com/docs/perf-mon) | [`dev.gitlive:firebase-perf:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-perf/1.11.0/pom) | [![1%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt) | -| [Crashlytics](https://firebase.google.com/docs/crashlytics) | [`dev.gitlive:firebase-crashlytics:1.11.0`](https://search.maven.org/artifact/dev.gitlive/firebase-crashlytics/1.11.0/pom) | [![80%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt) | +| [Authentication](https://firebase.google.com/docs/auth) | [`dev.gitlive:firebase-auth:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/1.11.1/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) | +| [Realtime Database](https://firebase.google.com/docs/database) | [`dev.gitlive:firebase-database:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-database/1.11.1/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) | +| [Cloud Firestore](https://firebase.google.com/docs/firestore) | [`dev.gitlive:firebase-firestore:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/1.11.1/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) | +| [Cloud Functions](https://firebase.google.com/docs/functions) | [`dev.gitlive:firebase-functions:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-functions/1.11.1/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt) | +| [Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) | [`dev.gitlive:firebase-messaging:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-messaging/1.11.1/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | +| [Cloud Storage](https://firebase.google.com/docs/storage) | [`dev.gitlive:firebase-storage:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-storage/1.11.1/pom) | [![40%](https://img.shields.io/badge/-40%25-orange?style=flat-square)](/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt) | +| [Installations](https://firebase.google.com/docs/projects/manage-installations) | [`dev.gitlive:firebase-installations:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-installations/1.11.1/pom) | [![90%](https://img.shields.io/badge/-90%25-green?style=flat-square)](/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt) | +| [Remote Config](https://firebase.google.com/docs/remote-config) | [`dev.gitlive:firebase-config:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-config/1.11.1/pom) | [![20%](https://img.shields.io/badge/-20%25-orange?style=flat-square)](/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt) | +| [Performance](https://firebase.google.com/docs/perf-mon) | [`dev.gitlive:firebase-perf:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-perf/1.11.1/pom) | [![1%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt) | +| [Crashlytics](https://firebase.google.com/docs/crashlytics) | [`dev.gitlive:firebase-crashlytics:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-crashlytics/1.11.1/pom) | [![80%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt) | Is the Firebase library or API you need missing? [Create an issue](https://github.com/GitLiveApp/firebase-kotlin-sdk/issues/new?labels=API+coverage&template=increase-api-coverage.md&title=Add+%5Bclass+name%5D.%5Bfunction+name%5D+to+%5Blibrary+name%5D+for+%5Bplatform+names%5D) to request additional API coverage or be awesome and [submit a PR](https://github.com/GitLiveApp/firebase-kotlin-sdk/fork) diff --git a/firebase-app/package.json b/firebase-app/package.json index 1e3d1ef80..81138414d 100644 --- a/firebase-app/package.json +++ b/firebase-app/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-app", - "version": "1.11.0", + "version": "1.11.1", "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.0", + "@gitlive/firebase-common": "1.11.1", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-auth/package.json b/firebase-auth/package.json index be336c079..c98caabfa 100644 --- a/firebase-auth/package.json +++ b/firebase-auth/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-auth", - "version": "1.11.0", + "version": "1.11.1", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-auth.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.11.0", + "@gitlive/firebase-app": "1.11.1", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-common/package.json b/firebase-common/package.json index 0f5e81321..e5826e53d 100644 --- a/firebase-common/package.json +++ b/firebase-common/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-common", - "version": "1.11.0", + "version": "1.11.1", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-common.js", "scripts": { diff --git a/firebase-config/package.json b/firebase-config/package.json index c005ac238..454740a67 100644 --- a/firebase-config/package.json +++ b/firebase-config/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-config", - "version": "1.11.0", + "version": "1.11.1", "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.0", + "@gitlive/firebase-app": "1.11.1", "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 360f26f85..efa72b3ed 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.0", + "@gitlive/firebase-app": "1.11.1", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-database/package.json b/firebase-database/package.json index ed9924756..090086c4a 100644 --- a/firebase-database/package.json +++ b/firebase-database/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-database", - "version": "1.11.0", + "version": "1.11.1", "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.0", + "@gitlive/firebase-app": "1.11.1", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-firestore/package.json b/firebase-firestore/package.json index fa2b48a23..eed05cf10 100644 --- a/firebase-firestore/package.json +++ b/firebase-firestore/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-firestore", - "version": "1.11.0", + "version": "1.11.1", "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.0", + "@gitlive/firebase-app": "1.11.1", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-functions/package.json b/firebase-functions/package.json index f60f381b7..0e5277af7 100644 --- a/firebase-functions/package.json +++ b/firebase-functions/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-functions", - "version": "1.11.0", + "version": "1.11.1", "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.0", + "@gitlive/firebase-app": "1.11.1", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-installations/package.json b/firebase-installations/package.json index ada558d69..b3c7f90a3 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.0", + "@gitlive/firebase-app": "1.11.1", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-perf/package.json b/firebase-perf/package.json index 3df0d5a60..33f81a510 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.0", + "@gitlive/firebase-app": "1.11.1", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-storage/package.json b/firebase-storage/package.json index c48b9b180..c2f243dc7 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.0", + "@gitlive/firebase-app": "1.11.1", "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 53a73a811..d83e642a5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -47,17 +47,17 @@ firebase-perf.skipJsTests=false firebase-storage.skipJsTests=false # Versions: -firebase-app.version=1.11.0 -firebase-auth.version=1.11.0 -firebase-common.version=1.11.0 -firebase-config.version=1.11.0 -firebase-database.version=1.11.0 -firebase-firestore.version=1.11.0 -firebase-functions.version=1.11.0 -firebase-installations.version=1.11.0 -firebase-perf.version=1.11.0 -firebase-crashlytics.version=1.11.0 -firebase-storage.version=1.11.0 +firebase-app.version=1.11.1 +firebase-auth.version=1.11.1 +firebase-common.version=1.11.1 +firebase-config.version=1.11.1 +firebase-database.version=1.11.1 +firebase-firestore.version=1.11.1 +firebase-functions.version=1.11.1 +firebase-installations.version=1.11.1 +firebase-perf.version=1.11.1 +firebase-crashlytics.version=1.11.1 +firebase-storage.version=1.11.1 # Dependencies Versions: gradlePluginVersion=8.1.3 From 49afe28778f11c041569b28e064745653a02abb2 Mon Sep 17 00:00:00 2001 From: Andrew Reed Date: Mon, 15 Jan 2024 10:15:04 +0000 Subject: [PATCH 10/27] Update README.md to reflect kotlin 1.9.20 (#462) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d8ced5365..0a75adaaa 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,8 @@ The Firebase Kotlin SDK uses Kotlin serialization to read and write custom class ```groovy plugins { - kotlin("multiplatform") version "1.8.21" // or kotlin("jvm") or any other kotlin plugin - kotlin("plugin.serialization") version "1.8.21" + kotlin("multiplatform") version "1.9.20" // or kotlin("jvm") or any other kotlin plugin + kotlin("plugin.serialization") version "1.9.20" } ``` From 4ad4d13cffc602fc36e32eefa01c624424d262e0 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 22 Jan 2024 17:42:14 +0100 Subject: [PATCH 11/27] Remove Async classes + add inline for builder funs --- .../gitlive/firebase/EncodeDecodeSettings.kt | 2 +- .../kotlin/dev/gitlive/firebase/decoders.kt | 4 +- .../kotlin/dev/gitlive/firebase/encoders.kt | 2 +- .../dev/gitlive/firebase/database/database.kt | 10 +- .../dev/gitlive/firebase/database/database.kt | 24 ++- .../dev/gitlive/firebase/database/database.kt | 10 +- .../dev/gitlive/firebase/database/database.kt | 8 +- .../gitlive/firebase/firestore/firestore.kt | 78 +++----- .../firebase/firestore/DeferredExtensions.kt | 17 -- .../gitlive/firebase/firestore/firestore.kt | 182 ++++++++---------- .../dev/gitlive/firebase/firestore/helpers.kt | 6 +- .../firestore/DeferredExtensionsTest.kt | 42 ---- .../firebase/firestore/FirestoreAsync.kt | 84 -------- .../gitlive/firebase/firestore/firestore.kt | 80 +++----- .../gitlive/firebase/firestore/firestore.kt | 56 ++---- .../gitlive/firebase/functions/functions.kt | 2 +- .../gitlive/firebase/functions/functions.kt | 8 +- .../gitlive/firebase/functions/functions.kt | 2 +- .../gitlive/firebase/functions/functions.kt | 2 +- 19 files changed, 201 insertions(+), 418 deletions(-) delete mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt delete mode 100644 firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt delete mode 100644 firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt 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 2ed6d5b3f..7e233600d 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt @@ -43,6 +43,6 @@ data class DecodeSettings internal constructor( class Builder { var serializersModule: SerializersModule = EmptySerializersModule() - internal fun build() = DecodeSettings(serializersModule) + fun build() = DecodeSettings(serializersModule) } } diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt index 226ec511e..55c7b8080 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt @@ -16,12 +16,12 @@ import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer inline fun decode(value: Any?): T = decode(value) {} -inline fun decode(value: Any?, noinline buildSettings: DecodeSettings.Builder.() -> Unit): T { +inline fun decode(value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T { val strategy = serializer() return decode(strategy as DeserializationStrategy, value, buildSettings) } fun decode(strategy: DeserializationStrategy, value: Any?): T = decode(strategy, value) {} -fun decode(strategy: DeserializationStrategy, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T { +inline fun decode(strategy: DeserializationStrategy, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T { require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" } return FirebaseDecoder(value, DecodeSettings.Builder().apply(buildSettings).build()).decodeSerializableValue(strategy) } diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 1d3d08dd2..8d68ce797 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -14,7 +14,7 @@ fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElement this.shouldEncodeElementDefault = shouldEncodeElementDefault } -fun encode(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = +inline fun encode(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = FirebaseEncoder(EncodeSettings.Builder().apply(buildSettings).build()).apply { encodeSerializableValue(strategy, value) }.value @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(value) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }")) 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 517142050..7b801de05 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 @@ -183,8 +183,8 @@ actual class DatabaseReference internal constructor( .run { Unit } @Suppress("UNCHECKED_CAST") - override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) = - android.updateChildren(encode(update, buildSettings) as Map) + override suspend fun updateEncodedChildren(encodedUpdate: Any?) = + android.updateChildren(encodedUpdate as Map) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @@ -236,7 +236,7 @@ actual class DataSnapshot internal constructor( actual inline fun value() = decode(value = android.value) - actual fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + actual inline fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = decode(strategy, android.value, buildSettings) actual fun child(path: String) = DataSnapshot(android.child(path), persistenceEnabled) @@ -262,8 +262,8 @@ actual class OnDisconnect internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) = - android.updateChildren(update.mapValues { (_, it) -> encode(it, buildSettings) }) + override suspend fun updateEncodedChildren(encodedUpdate: Map) = + android.updateChildren(encodedUpdate) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } } diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index 6be3699ff..d3fe52535 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 @@ -84,15 +84,20 @@ abstract class BaseDatabaseReference internal constructor(nativeQuery: NativeQue setValue(strategy, value) { shouldEncodeElementDefault = encodeDefaults } - suspend fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValueEncoded(encode(strategy, value, buildSettings)) + suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValueEncoded(encode(strategy, value, buildSettings)) - abstract suspend fun setValueEncoded(encodedValue: Any?) + @PublishedApi + internal abstract suspend fun setValueEncoded(encodedValue: Any?) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { shouldEncodeElementDefault = encodeDefaults }")) suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update) { shouldEncodeElementDefault = encodeDefaults } - abstract suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) + suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedChildren( + encode(update, buildSettings)) + + @PublishedApi + internal abstract suspend fun updateEncodedChildren(encodedUpdate: Any?) } expect class DatabaseReference : BaseDatabaseReference { @@ -112,7 +117,7 @@ expect class DataSnapshot { val ref: DatabaseReference val value: Any? inline fun value(): T - fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T + inline fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T fun child(path: String): DataSnapshot val hasChildren: Boolean val children: Iterable @@ -129,14 +134,19 @@ abstract class BaseOnDisconnect internal constructor() { @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { shouldEncodeElementDefault = encodeDefaults }")) suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = setValue(strategy, value) { shouldEncodeElementDefault = encodeDefaults } - suspend fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValue(encode(strategy, value, buildSettings)) - abstract suspend fun setValue(encodedValue: Any?) + suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValue(encode(strategy, value, buildSettings)) + + @PublishedApi + internal abstract suspend fun setValue(encodedValue: Any?) - abstract suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) + suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedChildren(update.mapValues { (_, it) -> encode(it, buildSettings) }) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { shouldEncodeElementDefault = encodeDefaults }")) suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update) { shouldEncodeElementDefault = encodeDefaults } + + @PublishedApi + internal abstract suspend fun updateEncodedChildren(encodedUpdate: Map) } expect class OnDisconnect : BaseOnDisconnect { 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 a6a014aa1..7c621c57f 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 @@ -151,8 +151,8 @@ actual class DatabaseReference internal constructor( } @Suppress("UNCHECKED_CAST") - override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) { - ios.await(persistenceEnabled) { updateChildValues(encode(update, buildSettings) as Map, it) } + override suspend fun updateEncodedChildren(encodedValue: Any?) { + ios.await(persistenceEnabled) { updateChildValues(encodedValue as Map, it) } } actual suspend fun removeValue() { @@ -198,7 +198,7 @@ actual class DataSnapshot internal constructor( actual inline fun value() = decode(value = ios.value) - actual fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + actual inline fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = decode(strategy, ios.value, buildSettings) actual fun child(path: String) = DataSnapshot(ios.childSnapshotForPath(path), persistenceEnabled) @@ -223,8 +223,8 @@ actual class OnDisconnect internal constructor( } @Suppress("UNCHECKED_CAST") - override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) { - ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it, buildSettings) } as Map, it) } + override suspend fun updateEncodedChildren(encodedUpdate: Map) { + ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate as Map, it) } } } diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index a1ad24b04..b075de95d 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 @@ -152,8 +152,8 @@ actual class DatabaseReference internal constructor( set(js, encodedValue).awaitWhileOnline(database) } - override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) = - rethrow { update(js, encode(update, buildSettings) ?: json()).awaitWhileOnline(database) } + override suspend fun updateEncodedChildren(encodedUpdate: Any?) = + rethrow { update(js, encodedUpdate ?: json()).awaitWhileOnline(database) } actual suspend fun runTransaction(strategy: KSerializer, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { @@ -201,8 +201,8 @@ actual class OnDisconnect internal constructor( override suspend fun setValue(encodedValue: Any?) = rethrow { js.set(encodedValue).awaitWhileOnline(database) } - override suspend fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit) = - rethrow { js.update(encode(update, buildSettings) ?: json()).awaitWhileOnline(database) } + override suspend fun updateEncodedChildren(encodedUpdate: Map) = + rethrow { js.update(encodedUpdate).awaitWhileOnline(database) } } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 13e4c1c07..060f81f42 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 @@ -30,15 +30,6 @@ actual val Firebase.firestore get() = actual fun Firebase.firestore(app: FirebaseApp) = FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance(app.android)) -@Suppress("DeferredIsResult") -@PublishedApi -internal fun Task.asUnitDeferred(): Deferred = CompletableDeferred() - .apply { - asDeferred().invokeOnCompletion { exception -> - if (exception == null) complete(Unit) else completeExceptionally(exception) - } - } - actual class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) { actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) @@ -91,8 +82,6 @@ val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when ( actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) : BaseWriteBatch() { - actual val async = Async(android) - override fun setEncoded( documentRef: DocumentReference, encodedData: Any, @@ -123,11 +112,8 @@ actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) : actual fun delete(documentRef: DocumentReference) = android.delete(documentRef.android).let { this } - actual suspend fun commit() = async.commit().await() - - @Suppress("DeferredIsResult") - actual class Async(private val android: com.google.firebase.firestore.WriteBatch) { - actual fun commit(): Deferred = android.commit().asUnitDeferred() + actual suspend fun commit() { + android.commit().await() } } @@ -183,13 +169,40 @@ actual class DocumentReference actual constructor(internal actual val nativeValu actual val parent: CollectionReference get() = CollectionReference(android.parent) - override val async = Async(android) - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) actual suspend fun get() = DocumentSnapshot(android.get().await()) + override suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) { + val task = (setOptions.android?.let { + android.set(encodedData, it) + } ?: android.set(encodedData)) + task.await() + } + + @Suppress("UNCHECKED_CAST") + override suspend fun updateEncoded(encodedData: Any) { + android.update(encodedData as Map).await() + } + + override suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { + android.update(encodedFieldsAndValues.toMap()) + }?.await() + } + + override suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + android.update(field, value, *moreFieldsAndValues) + }?.await() + } + + override suspend fun delete() { + android.delete().await() + } + actual val snapshots: Flow get() = snapshots() actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { @@ -205,29 +218,6 @@ actual class DocumentReference actual constructor(internal actual val nativeValu this === other || other is DocumentReference && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() - - @Suppress("DeferredIsResult") - class Async(@PublishedApi internal val android: NativeDocumentReference) : BaseDocumentReference.Async() { - - override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = (setOptions.android?.let { - android.set(encodedData, it) - } ?: android.set(encodedData)).asUnitDeferred() - - @Suppress("UNCHECKED_CAST") - override fun updateEncoded(encodedData: Any): Deferred = android.update(encodedData as Map).asUnitDeferred() - - override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { - android.update(encodedFieldsAndValues.toMap()) - }?.asUnitDeferred() ?: CompletableDeferred(Unit) - - override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - android.update(field, value, *moreFieldsAndValues) - }?.asUnitDeferred() ?: CompletableDeferred(Unit) - - override fun delete() = - android.delete().asUnitDeferred() - } } actual typealias NativeQuery = AndroidQuery @@ -345,7 +335,6 @@ actual class CollectionReference(override val android: com.google.firebase.fires actual val path: String get() = android.path - override val async = Async(android) actual val document: DocumentReference get() = DocumentReference(android.document()) @@ -355,10 +344,7 @@ actual class CollectionReference(override val android: com.google.firebase.fires actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) - @Suppress("DeferredIsResult") - class Async(@PublishedApi internal val android: com.google.firebase.firestore.CollectionReference) : BaseCollectionReference.Async() { - override fun addEncoded(data: Any): Deferred = android.add(data).asDeferred().convert(::DocumentReference) - } + override suspend fun addEncoded(data: Any) = DocumentReference(android.add(data).await()) } actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt deleted file mode 100644 index 00cae66af..000000000 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt +++ /dev/null @@ -1,17 +0,0 @@ -package dev.gitlive.firebase.firestore - -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred - -@PublishedApi -internal fun Deferred.convert(converter: (T) -> R): Deferred { - val deferred = CompletableDeferred() - invokeOnCompletion { exception -> - if (exception == null) { - deferred.complete(converter(getCompleted())) - } else { - deferred.completeExceptionally(exception) - } - } - return deferred -} 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 939ee1c2f..21cd30506 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 @@ -47,61 +47,66 @@ abstract class BaseTransaction { fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean = false): BaseTransaction = set(documentRef, data, merge) { shouldEncodeElementDefault = encodeDefaults } - fun set(documentRef: DocumentReference, data: Any, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = 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 = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, data, *mergeFields) { shouldEncodeElementDefault = encodeDefaults } - fun set(documentRef: DocumentReference, data: Any, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, data, *mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults } - fun set(documentRef: DocumentReference, data: Any, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults } - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = 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 = {}): BaseTransaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, *mergeFields) { shouldEncodeElementDefault = encodeDefaults } - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = 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 = {}): BaseTransaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, *mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults } - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = 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 = {}): BaseTransaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - protected abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseTransaction + abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseTransaction @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { shouldEncodeElementDefault = encodeDefaults }")) fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = update(documentRef, data) { shouldEncodeElementDefault = encodeDefaults } - 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, encode(data, buildSettings)!!) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data) { shouldEncodeElementDefault = encodeDefaults } - 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, encode(strategy, data, buildSettings)!!) @JvmName("updateFields") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) + inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @JvmName("updateFieldPaths") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) + inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) - protected abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction - protected abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction - protected abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction + @PublishedApi + internal abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction + + @PublishedApi + internal abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction + + @PublishedApi + internal abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction } expect class Transaction : BaseTransaction { @@ -236,27 +241,28 @@ abstract class BaseWriteBatch { fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults } - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + 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) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(documentRef, strategy, data, *mergeFields){ shouldEncodeElementDefault = encodeDefaults } - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + 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())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(documentRef, strategy, data, *mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults } - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + 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())) - abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseWriteBatch + @PublishedApi + internal abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseWriteBatch @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { shouldEncodeElementDefault = 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) { shouldEncodeElementDefault = encodeDefaults } inline fun update(documentRef: DocumentReference, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = @@ -266,30 +272,28 @@ abstract class BaseWriteBatch { fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(documentRef, strategy, data) { shouldEncodeElementDefault = encodeDefaults } - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encode(strategy, data, buildSettings)!!) @JvmName("updateField") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) + inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @JvmName("updateFieldPath") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) + inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) + + @PublishedApi + internal abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch - abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch + @PublishedApi + internal abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch - protected abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch - protected abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch + @PublishedApi + internal abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch } expect class WriteBatch : BaseWriteBatch { - val async: Async fun delete(documentRef: DocumentReference): WriteBatch suspend fun commit() - - @Suppress("DeferredIsResult") - class Async { - fun commit(): Deferred - } } /** A class representing a platform specific Firebase DocumentReference. */ @@ -297,103 +301,76 @@ expect class NativeDocumentReference abstract class BaseDocumentReference { - abstract class Async { - inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - - fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( - encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {})= setEncoded( - encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( - encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - - abstract fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred - - inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(encode(data, buildSettings)!!) - fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = update(encode(strategy, data, buildSettings)) - - @JvmName("updateFields") - fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) - @JvmName("updateFieldPaths") - fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) - - abstract fun updateEncoded(encodedData: Any): Deferred - protected abstract fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred - protected abstract fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred - - abstract fun delete(): Deferred - } - - abstract val async: Async - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, merge) { shouldEncodeElementDefault = encodeDefaults }")) suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(data, merge) { shouldEncodeElementDefault = encodeDefaults } - suspend inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.set(data, merge, buildSettings).await() + suspend inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(data, *mergeFields) { shouldEncodeElementDefault = encodeDefaults } - suspend inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.set(data, mergeFields = mergeFields, buildSettings = buildSettings).await() + suspend inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(data, *mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults } - suspend inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.set(data, mergeFieldPaths = mergeFieldPaths, buildSettings = buildSettings).await() + suspend inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults } - suspend fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.set(strategy, data, merge, buildSettings).await() + suspend inline fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( + encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = set(strategy, data, *mergeFields) { shouldEncodeElementDefault = encodeDefaults } - suspend fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.set(strategy, data, mergeFields = mergeFields, buildSettings).await() + suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( + encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = set(strategy, data, *mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults } - suspend fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.set(strategy, data, mergeFieldPaths = mergeFieldPaths, buildSettings).await() + suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( + encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + + @PublishedApi + internal abstract suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(data) { shouldEncodeElementDefault = encodeDefaults }")) suspend inline fun update(data: T, encodeDefaults: Boolean) = update(data) { shouldEncodeElementDefault = encodeDefaults } - suspend inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.update(data, buildSettings).await() + suspend inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(encode(data, buildSettings)!!) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = update(strategy, data) { shouldEncodeElementDefault = encodeDefaults } - suspend fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.update(strategy, data, buildSettings).await() + suspend inline fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(encode(strategy, data, buildSettings)!!) @JvmName("updateFields") - suspend fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.update(fieldsAndValues = fieldsAndValues, buildSettings).await() + suspend inline fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @JvmName("updateFieldPaths") - suspend fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - async.update(fieldsAndValues = fieldsAndValues, buildSettings).await() + suspend inline fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) + + @PublishedApi + internal abstract suspend fun updateEncoded(encodedData: Any) + + @PublishedApi + internal abstract suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) + + @PublishedApi + internal abstract suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) - suspend fun delete() = - async.delete().await() + abstract suspend fun delete() } /** A class representing a Firebase DocumentReference. */ @@ -413,31 +390,24 @@ expect class DocumentReference internal constructor(nativeValue: NativeDocumentR abstract class BaseCollectionReference(nativeQuery: NativeQuery) : Query(nativeQuery) { - @Suppress("DeferredIsResult") - abstract class Async { - inline fun add(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( - encode(data, buildSettings)!! - ) - fun add(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( - encode(strategy, data, buildSettings)!! - ) - - abstract fun addEncoded(data: Any): Deferred - } - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(data) { shouldEncodeElementDefault = encodeDefaults }")) suspend inline fun add(data: T, encodeDefaults: Boolean) = add(data) { shouldEncodeElementDefault = encodeDefaults } - suspend inline fun add(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = async.add(data, buildSettings).await() + suspend inline fun add(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( + encode(data, buildSettings)!! + ) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = add(strategy, data) { shouldEncodeElementDefault = encodeDefaults } - suspend fun add(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = async.add(strategy, data, buildSettings).await() + suspend inline fun add(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( + encode(strategy, data, buildSettings)!! + ) - abstract val async: Async + @PublishedApi + internal abstract suspend fun addEncoded(data: Any): DocumentReference } expect class CollectionReference : BaseCollectionReference { @@ -498,15 +468,17 @@ expect class DocumentChange { } abstract class BaseDocumentSnapshot { - inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, noinline buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(value = getEncoded(field, serverTimestampBehavior), buildSettings) - fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(field, serverTimestampBehavior), buildSettings) + 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) - abstract fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + @PublishedApi + internal abstract fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, noinline buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(encodedData(serverTimestampBehavior), buildSettings) - fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, 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) - abstract fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + @PublishedApi + internal abstract fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? } expect class DocumentSnapshot : BaseDocumentSnapshot { diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt index bac8e2e6a..147239e5f 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt @@ -5,20 +5,20 @@ import kotlin.jvm.JvmName //** Helper method to perform an update operation. */ @JvmName("performUpdateFields") -fun encodeFieldAndValue( +inline fun encodeFieldAndValue( fieldsAndValues: Array>, buildSettings: EncodeSettings.Builder.() -> Unit, ) = encodeFieldAndValue(fieldsAndValues, encodeField = { it }, encodeValue = { encode(it, buildSettings) }) /** Helper method to perform an update operation. */ @JvmName("performUpdateFieldPaths") -fun encodeFieldAndValue( +inline fun encodeFieldAndValue( fieldsAndValues: Array>, buildSettings: EncodeSettings.Builder.() -> Unit, ) = encodeFieldAndValue(fieldsAndValues, { it.encoded }, { encode(it, buildSettings) }) /** Helper method to perform an update operation in Android and JS. */ -internal fun encodeFieldAndValue( +inline fun encodeFieldAndValue( fieldsAndValues: Array>, encodeField: (T) -> K, encodeValue: (Any?) -> Any? diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt deleted file mode 100644 index 147a1c9d8..000000000 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package dev.gitlive.firebase.firestore - -import dev.gitlive.firebase.runTest -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class DeferredExtensionsTest { - - @Test - fun testConvert() = runTest { - val original = CompletableDeferred() - val converted = original.convert { it.toString() } - - original.complete(1) - assertEquals("1", converted.await()) - } - - @Test - fun testConvertCompleted() = runTest { - val original = CompletableDeferred(1) - val converted = original.convert { it.toString() } - - assertEquals("1", converted.await()) - } - - @Test - fun testCanceled() = runTest { - val original = CompletableDeferred() - val converted = original.convert { it.toString() } - - val result = runCatching { - original.cancel() - - converted.await() - } - assertTrue(result.isFailure) - assertTrue(result.exceptionOrNull() is CancellationException) - } -} diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt deleted file mode 100644 index 4641fdbac..000000000 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt +++ /dev/null @@ -1,84 +0,0 @@ -package dev.gitlive.firebase.firestore - -import dev.gitlive.firebase.* -import kotlinx.serialization.Serializable -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test - -@IgnoreJs -@IgnoreForAndroidUnitTest -class FirestoreAsync { - - lateinit var firestore: FirebaseFirestore - - @BeforeTest - fun initializeFirebase() { - val app = Firebase.apps(context).firstOrNull() ?: Firebase.initialize( - context, - FirebaseOptions( - applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", - apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", - databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", - storageBucket = "fir-kotlin-sdk.appspot.com", - projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) - ) - - firestore = Firebase.firestore(app).apply { - useEmulator(emulatorHost, 8080) - setSettings(persistenceEnabled = false) - } - } - - @AfterTest - fun deinitializeFirebase() = runBlockingTest { - Firebase.apps(context).forEach { - it.delete() - } - } - - @Serializable - data class TestData(val value: Int) - - @Test - fun asyncDocumentReferenceTest() = runTest { - fun getDocument() = firestore.collection("asyncDocumentReferenceTest") - .document("asyncDocumentReferenceTest") - - firestore.disableNetwork() - val update1 = getDocument().async.set(TestData(1)) - val update2 = getDocument().async.update(TestData(2)) - firestore.enableNetwork() - update1.await() - update2.await() - } - - @Test - fun asyncBatchTest() = runTest { - val batch = firestore.batch() - - fun getDocument() = firestore.collection("asyncBatchTest") - .document("asyncBatchTest") - - firestore.disableNetwork() - batch.set(getDocument(), TestData(1)) - batch.update(getDocument(), TestData(1)) - - val result = batch.async.commit() - firestore.enableNetwork() - - result.await() - } - - @Test - fun asyncCollectionAddTest() = runTest { - firestore.disableNetwork() - val result = firestore.collection("asyncCollectionAddTest").async - .add(TestData(1)) - firestore.enableNetwork() - - result.await() - } -} 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 7381120b1..14d04f538 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 @@ -75,8 +75,6 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { @Suppress("UNCHECKED_CAST") actual class WriteBatch(val ios: FIRWriteBatch) : BaseWriteBatch() { - actual val async = Async(ios) - override fun setEncoded( documentRef: DocumentReference, encodedData: Any, @@ -109,11 +107,7 @@ actual class WriteBatch(val ios: FIRWriteBatch) : BaseWriteBatch() { actual fun delete(documentRef: DocumentReference) = ios.deleteDocument(documentRef.ios).let { this } - actual suspend fun commit() = async.commit().await() - - actual class Async(@PublishedApi internal val ios: FIRWriteBatch) { - actual fun commit() = deferred { ios.commitWithCompletion(it) } - } + actual suspend fun commit() = await { ios.commitWithCompletion(it) } } @Suppress("UNCHECKED_CAST") @@ -163,33 +157,6 @@ actual typealias NativeDocumentReference = FIRDocumentReference @Serializable(with = DocumentReferenceSerializer::class) actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { - class Async(@PublishedApi internal val ios: NativeDocumentReference) : BaseDocumentReference.Async() { - - override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = deferred { - 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) - } - } - - override fun updateEncoded(encodedData: Any): Deferred = deferred { - ios.updateData(encodedData as Map, it) - } - - override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = deferred { - ios.updateData(encodedFieldsAndValues.toMap(), it) - } - - override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = deferred { - ios.updateData(encodedFieldsAndValues.toMap(), it) - } - - override fun delete() = - deferred { ios.deleteDocumentWithCompletion(it) } - } - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> snapshot?.let { trySend(DocumentSnapshot(snapshot)) } @@ -209,13 +176,35 @@ actual class DocumentReference actual constructor(internal actual val nativeValu actual val parent: CollectionReference get() = CollectionReference(ios.parent) - override val async = Async(nativeValue) actual fun collection(collectionPath: String) = CollectionReference(ios.collectionWithPath(collectionPath)) actual suspend fun get() = DocumentSnapshot(awaitResult { ios.getDocumentWithCompletion(it) }) + override 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) + } + } + + override suspend fun updateEncoded(encodedData: Any) = await { + ios.updateData(encodedData as Map, it) + } + + override suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = await { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } + + override suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) = await { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } + + override suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } + actual val snapshots get() = callbackFlow { val listener = ios.addSnapshotListener { snapshot, error -> snapshot?.let { trySend(DocumentSnapshot(snapshot)) } @@ -310,17 +299,13 @@ actual class CollectionReference(override val ios: FIRCollectionReference) : Bas actual val path: String get() = ios.path - override val async = Async(ios) - actual val document get() = DocumentReference(ios.documentWithAutoID()) actual val parent get() = ios.parent?.let{DocumentReference(it)} actual fun document(documentPath: String) = DocumentReference(ios.documentWithPath(documentPath)) - class Async(@PublishedApi internal val ios: FIRCollectionReference) : BaseCollectionReference.Async() { - override fun addEncoded(data: Any): Deferred = deferred { ios.addDocumentWithData(data as Map, it) }.convert(::DocumentReference) - } + override suspend fun addEncoded(data: Any) = DocumentReference(await { ios.addDocumentWithData(data as Map, it) }) } actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) @@ -481,18 +466,3 @@ suspend inline fun await(function: (callback: (NSError?) -> Unit) -> T): T { job.await() return result } - -@Suppress("DeferredIsResult") -@PublishedApi -internal inline fun deferred(function: (callback: (NSError?) -> Unit) -> T): Deferred { - val job = CompletableDeferred() - val callback = { error: NSError? -> - if(error == null) { - job.complete(Unit) - } else { - job.completeExceptionally(error.toException()) - } - } - val result = function(callback) - return job.convert { result } -} 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 291bf59e1..5f87ced08 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 @@ -97,8 +97,6 @@ val SetOptions.js: Json get() = when (this) { actual class WriteBatch(val js: JsWriteBatch) : BaseWriteBatch() { - actual val async = Async(js) - override fun setEncoded( documentRef: DocumentReference, encodedData: Any, @@ -130,12 +128,7 @@ actual class WriteBatch(val js: JsWriteBatch) : BaseWriteBatch() { rethrow { js.delete(documentRef.js) } .let { this } - actual suspend fun commit() = rethrow { async.commit().await() } - - @Suppress("DeferredIsResult") - actual class Async(private val js: JsWriteBatch) { - actual fun commit() = rethrow { js.commit().asDeferred() } - } + actual suspend fun commit() = rethrow { js.commit().await() } } actual class Transaction(val js: JsTransaction) : BaseTransaction() { @@ -194,8 +187,6 @@ actual class DocumentReference actual constructor(internal actual val nativeValu actual val parent: CollectionReference get() = rethrow { CollectionReference(js.parent) } - override val async = Async(nativeValue) - actual fun collection(collectionPath: String) = rethrow { CollectionReference(jsCollection(js, collectionPath)) } actual suspend fun get() = rethrow { DocumentSnapshot( getDoc(js).await()) } @@ -212,38 +203,37 @@ actual class DocumentReference actual constructor(internal actual val nativeValu awaitClose { unsubscribe() } } - override fun equals(other: Any?): Boolean = - this === other || other is DocumentReference && refEqual(nativeValue, other.nativeValue) - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = "DocumentReference(path=$path)" - - @Suppress("DeferredIsResult") - class Async(@PublishedApi internal val js: NativeDocumentReference) : BaseDocumentReference.Async() { - - override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = rethrow { - setDoc(js, encodedData, setOptions.js).asDeferred() - } + override suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) = rethrow { + setDoc(js, encodedData, setOptions.js).await() + } - override fun updateEncoded(encodedData: Any): Deferred = rethrow { jsUpdate(js, encodedData).asDeferred() } + override suspend fun updateEncoded(encodedData: Any) = rethrow { jsUpdate(js, encodedData).await() } - override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = rethrow { + override suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + rethrow { encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } ?.performUpdate { field, value, moreFieldsAndValues -> jsUpdate(js, field, value, *moreFieldsAndValues) } - ?.asDeferred() ?: CompletableDeferred(Unit) + ?.await() } + } - override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = rethrow { + override suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + rethrow { encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } ?.performUpdate { field, value, moreFieldsAndValues -> jsUpdate(js, field, value, *moreFieldsAndValues) - } - ?.asDeferred() ?: CompletableDeferred(Unit) + }?.await() } - - override fun delete() = rethrow { deleteDoc(js).asDeferred() } } + + override suspend fun delete() = rethrow { deleteDoc(js).await() } + + override fun equals(other: Any?): Boolean = + this === other || other is DocumentReference && refEqual(nativeValue, other.nativeValue) + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = "DocumentReference(path=$path)" } actual data class NativeQuery(val js: JsQuery) @@ -348,7 +338,6 @@ actual class CollectionReference(override val js: JsCollectionReference) : BaseC actual val path: String get() = rethrow { js.path } - override val async = Async(js) actual val document get() = rethrow { DocumentReference(doc(js)) } @@ -356,11 +345,8 @@ actual class CollectionReference(override val js: JsCollectionReference) : BaseC actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } - class Async(@PublishedApi internal val js: JsCollectionReference) : BaseCollectionReference.Async() { - - override fun addEncoded(data: Any): Deferred = rethrow { - addDoc(js, data).asDeferred().convert(::DocumentReference) - } + override suspend fun addEncoded(data: Any) = rethrow { + DocumentReference(addDoc(js, data).await()) } } 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 0c525b1ad..b051e721a 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 @@ -44,7 +44,7 @@ actual class HttpsCallableResult constructor(val android: com.google.firebase.fu actual inline fun data() = decode(value = android.data) - actual fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + actual inline fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = decode(strategy, android.data, buildSettings) } 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 5544fd30d..91a9c3226 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 @@ -24,8 +24,10 @@ abstract class BaseHttpsCallableReference { suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): HttpsCallableResult = invoke(strategy, data) { shouldEncodeElementDefault = encodeDefaults } - suspend operator fun invoke(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = invoke(encode(strategy, data, buildSettings)!!) - abstract suspend fun invoke(encodedData: Any): HttpsCallableResult + suspend inline operator fun invoke(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = invoke(encode(strategy, data, buildSettings)!!) + + @PublishedApi + internal abstract suspend fun invoke(encodedData: Any): HttpsCallableResult } expect class HttpsCallableReference : BaseHttpsCallableReference { @@ -34,7 +36,7 @@ expect class HttpsCallableReference : BaseHttpsCallableReference { expect class HttpsCallableResult { inline fun data(): T - fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T + inline fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T } /** Returns the [FirebaseFunctions] instance of the default [FirebaseApp]. */ 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 102696fc6..1104c8027 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 @@ -50,7 +50,7 @@ actual class HttpsCallableResult constructor(val ios: FIRHTTPSCallableResult) { actual inline fun data() = decode(value = ios.data()) - actual fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + actual inline fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = decode(strategy, ios.data(), buildSettings) } 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 64ea488e0..b340d2aa5 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 @@ -47,7 +47,7 @@ actual class HttpsCallableResult constructor(val js: JsHttpsCallableResult) { actual inline fun data() = rethrow { decode(value = js.data) } - actual fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + actual inline fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = rethrow { decode(strategy, js.data, buildSettings) } } From 46d7c1705b9aa385a96b3a042e010bb00a08b586 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 22 Jan 2024 20:53:19 +0100 Subject: [PATCH 12/27] Fixed Database runTransaction --- .../gitlive/firebase/EncodeDecodeSettings.kt | 38 ++++++-- .../kotlin/dev/gitlive/firebase/decoders.kt | 16 +++- .../kotlin/dev/gitlive/firebase/encoders.kt | 19 ++-- .../firebase/reencodeTransformation.kt | 15 ++++ .../dev/gitlive/firebase/database/database.kt | 6 +- .../dev/gitlive/firebase/database/database.kt | 3 +- .../dev/gitlive/firebase/database/database.kt | 86 +++++++++---------- .../dev/gitlive/firebase/database/database.kt | 6 +- .../dev/gitlive/firebase/database/database.kt | 8 +- 9 files changed, 123 insertions(+), 74 deletions(-) create mode 100644 firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt 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 7e233600d..dc12f3b59 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt @@ -23,12 +23,17 @@ data class EncodeSettings internal constructor( val shouldEncodeElementDefault: Boolean, override val serializersModule: SerializersModule, ) : EncodeDecodeSettings() { - class Builder { - var shouldEncodeElementDefault: Boolean = true - var serializersModule: SerializersModule = EmptySerializersModule() - @PublishedApi - internal fun build() = EncodeSettings(shouldEncodeElementDefault, serializersModule) + interface Builder { + var shouldEncodeElementDefault: Boolean + var serializersModule: SerializersModule + + } + + @PublishedApi + internal class BuilderImpl : Builder { + override var shouldEncodeElementDefault: Boolean = true + override var serializersModule: SerializersModule = EmptySerializersModule() } } @@ -40,9 +45,26 @@ data class DecodeSettings internal constructor( override val serializersModule: SerializersModule = EmptySerializersModule(), ) : EncodeDecodeSettings() { - class Builder { - var serializersModule: SerializersModule = EmptySerializersModule() + interface Builder { + var serializersModule: SerializersModule + } - fun build() = DecodeSettings(serializersModule) + @PublishedApi + internal class BuilderImpl : Builder { + override var serializersModule: SerializersModule = EmptySerializersModule() } } + +interface EncodeDecodeSettingsBuilder : EncodeSettings.Builder, DecodeSettings.Builder + +@PublishedApi +internal class EncodeDecodeSettingsBuilderImpl : EncodeDecodeSettingsBuilder { + + override var shouldEncodeElementDefault: Boolean = true + override var serializersModule: SerializersModule = EmptySerializersModule() +} + +@PublishedApi +internal fun EncodeSettings.Builder.buildEncodeSettings(): EncodeSettings = EncodeSettings(shouldEncodeElementDefault, serializersModule) +@PublishedApi +internal fun DecodeSettings.Builder.buildDecodeSettings(): DecodeSettings = DecodeSettings(serializersModule) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt index 55c7b8080..8b650422c 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt @@ -16,14 +16,22 @@ import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer inline fun decode(value: Any?): T = decode(value) {} -inline fun decode(value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T { +inline fun decode(value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T = + decode(value, DecodeSettings.BuilderImpl().apply(buildSettings).buildDecodeSettings()) + +@PublishedApi +internal inline fun decode(value: Any?, decodeSettings: DecodeSettings): T { val strategy = serializer() - return decode(strategy as DeserializationStrategy, value, buildSettings) + return decode(strategy as DeserializationStrategy, value, decodeSettings) } fun decode(strategy: DeserializationStrategy, value: Any?): T = decode(strategy, value) {} -inline fun decode(strategy: DeserializationStrategy, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T { +inline fun decode(strategy: DeserializationStrategy, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T = + decode(strategy, value, DecodeSettings.BuilderImpl().apply(buildSettings).buildDecodeSettings()) + +@PublishedApi +internal fun decode(strategy: DeserializationStrategy, value: Any?, decodeSettings: DecodeSettings): T { require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" } - return FirebaseDecoder(value, DecodeSettings.Builder().apply(buildSettings).build()).decodeSerializableValue(strategy) + return FirebaseDecoder(value, decodeSettings).decodeSerializableValue(strategy) } expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder expect fun getPolymorphicType(value: Any?, discriminator: String): String diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 8d68ce797..5d2389522 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -14,15 +14,24 @@ fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElement this.shouldEncodeElementDefault = shouldEncodeElementDefault } -inline fun encode(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = - FirebaseEncoder(EncodeSettings.Builder().apply(buildSettings).build()).apply { encodeSerializableValue(strategy, value) }.value +inline fun encode(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit) = + encode(strategy, value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) + +@PublishedApi +internal inline fun encode(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings): Any? = + FirebaseEncoder(encodeSettings).apply { encodeSerializableValue(strategy, value) }.value @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(value) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }")) inline fun encode(value: T, shouldEncodeElementDefault: Boolean): Any? = encode(value) { this.shouldEncodeElementDefault = shouldEncodeElementDefault } -inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = value?.let { - FirebaseEncoder(EncodeSettings.Builder().apply(buildSettings).build()).apply { + +inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = + encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) + +@PublishedApi +internal inline fun encode(value: T, encodeSettings: EncodeSettings): Any? = value?.let { + FirebaseEncoder(encodeSettings).apply { if (it is ValueWithSerializer<*> && it.value is T) { @Suppress("UNCHECKED_CAST") (it as ValueWithSerializer).let { @@ -49,7 +58,7 @@ class FirebaseEncoder( ) : Encoder { constructor(shouldEncodeElementDefault: Boolean) : this( - EncodeSettings.Builder().apply { this.shouldEncodeElementDefault = shouldEncodeElementDefault }.build() + EncodeSettings.BuilderImpl().apply { this.shouldEncodeElementDefault = shouldEncodeElementDefault }.buildEncodeSettings() ) var value: Any? = null diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt new file mode 100644 index 000000000..6812732c0 --- /dev/null +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt @@ -0,0 +1,15 @@ +package dev.gitlive.firebase + +import kotlinx.serialization.KSerializer + +inline fun reencodeTransformation(value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? { + val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder) + val oldValue: T = decode(value, encodeDecodeSettingsBuilder.buildDecodeSettings()) + return encode(transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings()) +} + +inline fun reencodeTransformation(strategy: KSerializer, value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? { + val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder) + val oldValue: T = decode(strategy, value, encodeDecodeSettingsBuilder.buildDecodeSettings()) + return encode(strategy, transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings()) +} \ No newline at end of file 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 7b801de05..f6e13b372 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 @@ -192,14 +192,12 @@ actual class DatabaseReference internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - actual suspend fun runTransaction(strategy: KSerializer, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() android.runTransaction(object : Transaction.Handler { override fun doTransaction(currentData: MutableData): Transaction.Result { - currentData.value = currentData.value?.let { - transactionUpdate(decode(strategy, it, buildSettings)) - } + currentData.value = reencodeTransformation(strategy, currentData.value, buildSettings, transactionUpdate) return Transaction.success(currentData) } 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 d3fe52535..75b8ada24 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 @@ -5,6 +5,7 @@ package dev.gitlive.firebase.database import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder import dev.gitlive.firebase.EncodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp @@ -108,7 +109,7 @@ expect class DatabaseReference : BaseDatabaseReference { suspend fun removeValue() - suspend fun runTransaction(strategy: KSerializer, buildSettings: DecodeSettings.Builder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot + suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot } expect class DataSnapshot { 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 00a8f85dc..29ee7e35b 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 @@ -79,51 +79,47 @@ class FirebaseDatabaseTest { assertEquals(3, firebaseDatabaseChildCount) } -// @Test -// fun testBasicIncrementTransaction() = runTest { -// val data = DatabaseTest("PostOne", 2) -// val userRef = database.reference("users/user_1/post_id_1") -// setupDatabase(userRef, data, DatabaseTest.serializer()) -// -// // Check database before transaction -// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) -// assertEquals(data.title, userDocBefore.title) -// assertEquals(data.likes, userDocBefore.likes) -// -// // Run transaction -// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes + 1) } -// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) -// -// // Check the database after transaction -// assertEquals(data.title, userDocAfter.title) -// assertEquals(data.likes + 1, userDocAfter.likes) -// -// // cleanUp Firebase -// cleanUp() -// } -// -// @Test -// fun testBasicDecrementTransaction() = runTest { -// val data = DatabaseTest("PostTwo", 2) -// val userRef = database.reference("users/user_1/post_id_2") -// setupDatabase(userRef, data, DatabaseTest.serializer()) -// -// // Check database before transaction -// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) -// assertEquals(data.title, userDocBefore.title) -// assertEquals(data.likes, userDocBefore.likes) -// -// // Run transaction -// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes - 1) } -// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) -// -// // Check the database after transaction -// assertEquals(data.title, userDocAfter.title) -// assertEquals(data.likes - 1, userDocAfter.likes) -// -// // cleanUp Firebase -// cleanUp() -// } + @Test + fun testBasicIncrementTransaction() = runTest { + ensureDatabaseConnected() + val data = DatabaseTest("PostOne", 2) + val userRef = database.reference("users/user_1/post_id_1") + setupDatabase(userRef, data, DatabaseTest.serializer()) + + // Check database before transaction + val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) + assertEquals(data.title, userDocBefore.title) + assertEquals(data.likes, userDocBefore.likes) + + // Run transaction + val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes + 1) } + val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) + + // Check the database after transaction + assertEquals(data.title, userDocAfter.title) + assertEquals(data.likes + 1, userDocAfter.likes) + } + + @Test + fun testBasicDecrementTransaction() = runTest { + ensureDatabaseConnected() + val data = DatabaseTest("PostTwo", 2) + val userRef = database.reference("users/user_1/post_id_2") + setupDatabase(userRef, data, DatabaseTest.serializer()) + + // Check database before transaction + val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) + assertEquals(data.title, userDocBefore.title) + assertEquals(data.likes, userDocBefore.likes) + + // Run transaction + val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes - 1) } + val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) + + // Check the database after transaction + assertEquals(data.title, userDocAfter.title) + assertEquals(data.likes - 1, userDocAfter.likes) + } @Test fun testSetServerTimestamp() = runTest { 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 7c621c57f..a264705f8 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 @@ -159,13 +159,11 @@ actual class DatabaseReference internal constructor( ios.await(persistenceEnabled) { removeValueWithCompletionBlock(it) } } - actual suspend fun runTransaction(strategy: KSerializer, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() ios.runTransactionBlock( block = { firMutableData -> - firMutableData?.value = firMutableData?.value?.let { - transactionUpdate(decode(strategy, it, buildSettings)) - } + firMutableData?.value = reencodeTransformation(strategy, firMutableData?.value, buildSettings, transactionUpdate) FIRTransactionResult.successWithValue(firMutableData!!) }, andCompletionBlock = { error, _, snapshot -> 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 b075de95d..fbc6c078c 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 @@ -156,8 +156,10 @@ actual class DatabaseReference internal constructor( rethrow { update(js, encodedUpdate ?: json()).awaitWhileOnline(database) } - actual suspend fun runTransaction(strategy: KSerializer, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { - return DataSnapshot(jsRunTransaction(js, transactionUpdate).awaitWhileOnline(database).snapshot, database) + actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { + return DataSnapshot(jsRunTransaction(js, transactionUpdate = { currentData -> + reencodeTransformation(strategy, currentData ?: json(), buildSettings, transactionUpdate) + }).awaitWhileOnline(database).snapshot, database) } } @@ -173,7 +175,7 @@ actual class DataSnapshot internal constructor( actual inline fun value() = rethrow { decode(value = js.`val`()) } - actual fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = + actual inline fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit) = rethrow { decode(strategy, js.`val`(), buildSettings) } actual val exists get() = rethrow { js.exists() } From bf709a3c507361b64cb0d00720c3220ee8368496 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Tue, 23 Jan 2024 10:58:50 +0100 Subject: [PATCH 13/27] Fixed Android crash, updated tests + cleanup --- .../kotlin/dev/gitlive/firebase/decoders.kt | 1 - .../kotlin/dev/gitlive/firebase/encoders.kt | 8 +- .../firebase/reencodeTransformation.kt | 15 +- .../dev/gitlive/firebase/EncodersTest.kt | 157 ++++++++++++++++-- .../dev/gitlive/firebase/database/database.kt | 60 +++++-- .../dev/gitlive/firebase/database/database.kt | 5 +- .../dev/gitlive/firebase/database/database.kt | 17 +- .../dev/gitlive/firebase/database/database.kt | 27 ++- .../dev/gitlive/firebase/database/database.kt | 31 +++- .../gitlive/firebase/firestore/firestore.kt | 28 ++-- .../gitlive/firebase/firestore/firestore.kt | 1 - .../gitlive/firebase/firestore/firestore.kt | 34 +++- 12 files changed, 309 insertions(+), 75 deletions(-) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt index 8b650422c..08129e9af 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt @@ -11,7 +11,6 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 5d2389522..33178187c 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -4,9 +4,11 @@ package dev.gitlive.firebase -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.descriptors.SerialDescriptor +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) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }")) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt index 6812732c0..7c9704157 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/reencodeTransformation.kt @@ -2,14 +2,21 @@ package dev.gitlive.firebase import kotlinx.serialization.KSerializer -inline fun reencodeTransformation(value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? { +inline fun reencodeTransformation(value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit = {}, transform: (T) -> T): Any? { val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder) val oldValue: T = decode(value, encodeDecodeSettingsBuilder.buildDecodeSettings()) - return encode(transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings()) + return encode( + transform(oldValue), + encodeDecodeSettingsBuilder.buildEncodeSettings() + ) } -inline fun reencodeTransformation(strategy: KSerializer, value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? { +inline fun reencodeTransformation(strategy: KSerializer, value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit = {}, transform: (T) -> T): Any? { val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder) val oldValue: T = decode(strategy, value, encodeDecodeSettingsBuilder.buildDecodeSettings()) - return encode(strategy, transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings()) + return encode( + strategy, + transform(oldValue), + encodeDecodeSettingsBuilder.buildEncodeSettings() + ) } \ No newline at end of file diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index f1feedf5b..99026654e 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -7,14 +7,11 @@ package dev.gitlive.firebase import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNull -import dev.gitlive.firebase.nativeAssertEquals -import dev.gitlive.firebase.nativeMapOf import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.modules.SerializersModule +import kotlin.test.Test +import kotlin.test.assertEquals @Serializable object TestObject { @@ -62,7 +59,7 @@ class EncodersTest { @Test fun encodeDecodeList() { val list = listOf("One", "Two", "Three") - val encoded = encode(list, shouldEncodeElementDefault = true) + val encoded = encode>(list) { shouldEncodeElementDefault = true } nativeAssertEquals(nativeListOf("One", "Two", "Three"), encoded) @@ -73,7 +70,7 @@ class EncodersTest { @Test fun encodeDecodeMap() { val map = mapOf("key" to "value", "key2" to "value2", "key3" to "value3") - val encoded = encode(map, shouldEncodeElementDefault = true) + val encoded = encode>(map) { shouldEncodeElementDefault = true } nativeAssertEquals(nativeMapOf("key" to "value", "key2" to "value2", "key3" to "value3"), encoded) @@ -83,7 +80,7 @@ class EncodersTest { @Test fun encodeDecodeObject() { - val encoded = encode(TestObject.serializer(), TestObject, shouldEncodeElementDefault = false) + val encoded = encode(TestObject.serializer(), TestObject) { shouldEncodeElementDefault = false } nativeAssertEquals(nativeMapOf(), encoded) val decoded = decode(TestObject.serializer(), encoded) @@ -93,7 +90,7 @@ class EncodersTest { @Test fun encodeDecodeClass() { val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) - val encoded = encode(TestData.serializer(), testDataClass, shouldEncodeElementDefault = false) + val encoded = encode(TestData.serializer(), testDataClass) { shouldEncodeElementDefault = false } nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true), encoded) @@ -104,7 +101,7 @@ class EncodersTest { @Test fun encodeDecodeClassNullableValue() { val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, nullableBool = true) - val encoded = encode(TestData.serializer(), testDataClass, shouldEncodeElementDefault = true) + val encoded = encode(TestData.serializer(), testDataClass) { shouldEncodeElementDefault = true } nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), encoded) @@ -116,7 +113,7 @@ class EncodersTest { fun encodeDecodeGenericClass() { val innerClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) val genericClass = GenericClass(innerClass) - val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass, shouldEncodeElementDefault = true) + val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass) { shouldEncodeElementDefault = true } nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null)), encoded) @@ -188,4 +185,142 @@ class EncodersTest { } assertEquals(nestedClass, decoded) } + + @Test + fun reencodeTransformationList() { + val reencoded = reencodeTransformation>(nativeListOf("One", "Two", "Three")) { + assertEquals(listOf("One", "Two", "Three"), it) + it.map { value -> "new$value" } + } + nativeAssertEquals(nativeListOf("newOne", "newTwo", "newThree"), reencoded) + } + + @Test + fun reencodeTransformationMap() { + val reencoded = reencodeTransformation>(nativeMapOf("key" to "value", "key2" to "value2", "key3" to "value3")) { + assertEquals(mapOf("key" to "value", "key2" to "value2", "key3" to "value3"), it) + it.mapValues { (_, value) -> "new-$value" } + } + + nativeAssertEquals(nativeMapOf("key" to "new-value", "key2" to "new-value2", "key3" to "new-value3"), reencoded) + } + + @Test + fun reencodeTransformationObject() { + val reencoded = reencodeTransformation(nativeMapOf(), { shouldEncodeElementDefault = false }) { + assertEquals(TestObject, it) + it + } + nativeAssertEquals(nativeMapOf(), reencoded) + } + + @Test + fun reencodeTransformationClass() { + val reencoded = reencodeTransformation( + nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), + { shouldEncodeElementDefault = false } + ) { + assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true), it) + it.copy(map = mapOf("newKey" to "newValue"), nullableBool = null) + } + + nativeAssertEquals(nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true), reencoded) + } + + @Test + fun reencodeTransformationNullableValue() { + val reencoded = reencodeTransformation( + nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), + { shouldEncodeElementDefault = false } + ) { + assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true), it) + null + } + + nativeAssertEquals(null, reencoded) + } + + @Test + fun reencodeTransformationGenericClass() { + val reencoded = reencodeTransformation( + GenericClass.serializer(TestData.serializer()), + nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to false)), + { shouldEncodeElementDefault = false } + ) { + assertEquals( + GenericClass(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = false)), + it + ) + GenericClass(it.inner.copy(map = mapOf("newKey" to "newValue"), nullableBool = null)) + } + + nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true)), reencoded) + } + + @Test + fun reencodeTransformationSealedClass() { + val reencoded = reencodeTransformation(SealedClass.serializer(), nativeMapOf("type" to "test", "value" to "value")) { + assertEquals(SealedClass.Test("value"), it) + SealedClass.Test("newTest") + } + + nativeAssertEquals(nativeMapOf("type" to "test", "value" to "newTest"), reencoded) + } + + @Test + fun reencodeTransformationPolymorphicClass() { + val module = SerializersModule { + polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) + } + + val reencoded = reencodeTransformation( + AbstractClass.serializer(), + nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true), + builder = { + serializersModule = module + } + ) { + assertEquals(ImplementedClass("value", true), it) + ImplementedClass("new-${it.value}", false) + } + + nativeAssertEquals(nativeMapOf("type" to "implemented", "value" to "new-value", "otherValue" to false), reencoded) + } + + @Test + fun reencodeTransformationNestedClass() { + val module = SerializersModule { + polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) + } + + val sealedClass: SealedClass = SealedClass.Test("value") + val abstractClass: AbstractClass = ImplementedClass("value", true) + val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) + val encoded = encode(NestedClass.serializer(), nestedClass) { + shouldEncodeElementDefault = true + serializersModule = module + } + + val reencoded = reencodeTransformation(NestedClass.serializer(), encoded, builder = { + shouldEncodeElementDefault = true + serializersModule = module + }) { + assertEquals(nestedClass, it) + it.copy(sealed = SealedClass.Test("newValue")) + } + + val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") + val abstractEncoded = nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true) + nativeAssertEquals( + nativeMapOf( + "sealed" to nativeMapOf("type" to "test", "value" to "newValue"), + "abstract" to abstractEncoded, + "sealedList" to nativeListOf(sealedEncoded), + "abstractList" to nativeListOf(abstractEncoded), + "sealedMap" to nativeMapOf(sealedEncoded to sealedEncoded), + "abstractMap" to nativeMapOf(abstractEncoded to abstractEncoded) + ), + reencoded + ) + } } 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 f6e13b372..55f440699 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 @@ -5,18 +5,36 @@ package dev.gitlive.firebase.database import com.google.android.gms.tasks.Task -import com.google.firebase.database.* -import dev.gitlive.firebase.* +import com.google.firebase.database.ChildEventListener +import com.google.firebase.database.DatabaseError +import com.google.firebase.database.Logger +import com.google.firebase.database.MutableData +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.Firebase +import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type import dev.gitlive.firebase.database.FirebaseDatabase.Companion.FirebaseDatabase +import dev.gitlive.firebase.decode +import dev.gitlive.firebase.reencodeTransformation import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer -import java.util.* +import java.util.WeakHashMap import kotlin.time.Duration.Companion.seconds suspend fun Task.awaitWhileOnline(database: FirebaseDatabase): T = @@ -118,18 +136,18 @@ actual open class Query internal actual constructor( actual val valueEvents: Flow get() = callbackFlow { - val listener = object : ValueEventListener { - override fun onDataChange(snapshot: com.google.firebase.database.DataSnapshot) { - trySendBlocking(DataSnapshot(snapshot, persistenceEnabled)) - } + val listener = object : ValueEventListener { + override fun onDataChange(snapshot: com.google.firebase.database.DataSnapshot) { + trySendBlocking(DataSnapshot(snapshot, persistenceEnabled)) + } - override fun onCancelled(error: com.google.firebase.database.DatabaseError) { - close(error.toException()) + override fun onCancelled(error: com.google.firebase.database.DatabaseError) { + close(error.toException()) + } } + android.addValueEventListener(listener) + awaitClose { android.removeEventListener(listener) } } - android.addValueEventListener(listener) - awaitClose { android.removeEventListener(listener) } - } actual fun childEvents(vararg types: Type): Flow = callbackFlow { val listener = object : ChildEventListener { @@ -192,12 +210,22 @@ actual class DatabaseReference internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } + @OptIn(ExperimentalSerializationApi::class) actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() android.runTransaction(object : Transaction.Handler { override fun doTransaction(currentData: MutableData): Transaction.Result { - currentData.value = reencodeTransformation(strategy, currentData.value, buildSettings, transactionUpdate) + val valueToReencode = currentData.value + // Value may be null initially, so only reencode if this is allowed + if (strategy.descriptor.isNullable || valueToReencode != null) { + currentData.value = reencodeTransformation( + strategy, + valueToReencode, + buildSettings, + transactionUpdate + ) + } return Transaction.success(currentData) } @@ -257,8 +285,8 @@ actual class OnDisconnect internal constructor( .run { Unit } override suspend fun setValue(encodedValue: Any?) = android.setValue(encodedValue) - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } + .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } + .run { Unit } override suspend fun updateEncodedChildren(encodedUpdate: Map) = android.updateChildren(encodedUpdate) 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 75b8ada24..27fa9feba 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 @@ -9,7 +9,10 @@ import dev.gitlive.firebase.EncodeDecodeSettingsBuilder import dev.gitlive.firebase.EncodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.database.ChildEvent.Type.* +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 kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy 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 29ee7e35b..ba3f7be59 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 @@ -1,13 +1,22 @@ package dev.gitlive.firebase.database -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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationStrategy -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue import kotlin.time.Duration.Companion.minutes expect val emulatorHost: String @@ -92,7 +101,7 @@ class FirebaseDatabaseTest { assertEquals(data.likes, userDocBefore.likes) // Run transaction - val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes + 1) } + val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes + 1) } val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) // Check the database after transaction @@ -113,7 +122,7 @@ class FirebaseDatabaseTest { assertEquals(data.likes, userDocBefore.likes) // Run transaction - val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes - 1) } + val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes - 1) } val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) // Check the database after transaction 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 a264705f8..b6451234e 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 @@ -4,11 +4,27 @@ package dev.gitlive.firebase.database -import cocoapods.FirebaseDatabase.* -import cocoapods.FirebaseDatabase.FIRDataEventType.* -import dev.gitlive.firebase.* +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeChildAdded +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeChildChanged +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeChildMoved +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeChildRemoved +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeValue +import cocoapods.FirebaseDatabase.FIRDataSnapshot +import cocoapods.FirebaseDatabase.FIRDatabase +import cocoapods.FirebaseDatabase.FIRDatabaseQuery +import cocoapods.FirebaseDatabase.FIRDatabaseReference +import cocoapods.FirebaseDatabase.FIRTransactionResult +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type -import dev.gitlive.firebase.database.ChildEvent.Type.* +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 kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose @@ -19,11 +35,8 @@ import kotlinx.coroutines.flow.produceIn import kotlinx.coroutines.selects.select import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationStrategy import platform.Foundation.NSError import platform.Foundation.allObjects -import kotlin.collections.component1 -import kotlin.collections.component2 actual val Firebase.database by lazy { FirebaseDatabase(FIRDatabase.database()) } 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 fbc6c078c..218dd2958 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 @@ -4,11 +4,34 @@ package dev.gitlive.firebase.database -import dev.gitlive.firebase.* +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.database.externals.* -import kotlinx.coroutines.* +import dev.gitlive.firebase.database.externals.CancelCallback +import dev.gitlive.firebase.database.externals.ChangeSnapshotCallback +import dev.gitlive.firebase.database.externals.Database +import dev.gitlive.firebase.database.externals.child +import dev.gitlive.firebase.database.externals.connectDatabaseEmulator +import dev.gitlive.firebase.database.externals.enableLogging +import dev.gitlive.firebase.database.externals.getDatabase +import dev.gitlive.firebase.database.externals.onChildAdded +import dev.gitlive.firebase.database.externals.onChildChanged +import dev.gitlive.firebase.database.externals.onChildMoved +import dev.gitlive.firebase.database.externals.onChildRemoved +import dev.gitlive.firebase.database.externals.onDisconnect +import dev.gitlive.firebase.database.externals.onValue +import dev.gitlive.firebase.database.externals.push +import dev.gitlive.firebase.database.externals.query +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 kotlinx.coroutines.asDeferred import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.produceIn @@ -87,7 +110,7 @@ actual open class Query internal actual constructor( val callback: ChangeSnapshotCallback = { snapshot, previousChildName -> trySend( ChildEvent( - DataSnapshot(snapshot, database), + DataSnapshot(snapshot, database), type, previousChildName ) 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 060f81f42..3a1a57119 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 @@ -5,24 +5,18 @@ @file:JvmName("android") package dev.gitlive.firebase.firestore -import com.google.android.gms.tasks.Task -import com.google.firebase.firestore.* -import dev.gitlive.firebase.* -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred +import com.google.firebase.firestore.MetadataChanges +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.tasks.asDeferred +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.tasks.await -import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationStrategy - -import com.google.firebase.firestore.Query as AndroidQuery import com.google.firebase.firestore.FieldPath as AndroidFieldPath import com.google.firebase.firestore.Filter as AndroidFilter +import com.google.firebase.firestore.Query as AndroidQuery actual val Firebase.firestore get() = FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance()) @@ -58,12 +52,12 @@ actual class FirebaseFirestore(val android: com.google.firebase.firestore.Fireba actual fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings.Builder().also { builder -> - persistenceEnabled?.let { builder.setPersistenceEnabled(it) } - sslEnabled?.let { builder.isSslEnabled = it } - host?.let { builder.host = it } - cacheSizeBytes?.let { builder.cacheSizeBytes = it } - }.build() - } + persistenceEnabled?.let { builder.setPersistenceEnabled(it) } + sslEnabled?.let { builder.isSslEnabled = it } + host?.let { builder.host = it } + cacheSizeBytes?.let { builder.cacheSizeBytes = it } + }.build() + } actual suspend fun disableNetwork() = android.disableNetwork().await().run { } 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 21cd30506..8b774c02e 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 @@ -5,7 +5,6 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.* -import kotlinx.coroutines.Deferred import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.Serializable 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 5f87ced08..439ea9ae1 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -4,15 +4,37 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* -import dev.gitlive.firebase.firestore.externals.* -import kotlinx.coroutines.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.firestore.externals.Firestore +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.enableIndexedDbPersistence +import dev.gitlive.firebase.firestore.externals.getDoc +import dev.gitlive.firebase.firestore.externals.getDocs +import dev.gitlive.firebase.firestore.externals.getFirestore +import dev.gitlive.firebase.firestore.externals.initializeFirestore +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.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.serialization.DeserializationStrategy +import kotlinx.coroutines.promise import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationStrategy import kotlin.js.Json import kotlin.js.json import dev.gitlive.firebase.firestore.externals.CollectionReference as JsCollectionReference @@ -490,7 +512,7 @@ fun errorToException(e: dynamic) = (e?.code ?: e?.message ?: "") FirebaseFirestoreException(e, FirestoreExceptionCode.UNKNOWN) } } -} + } // from: https://discuss.kotlinlang.org/t/how-to-access-native-js-object-as-a-map-string-any/509/8 fun entriesOf(jsObject: dynamic): List> = From 30215024c2150d7b4657307d007cc4bef2b3fff8 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Tue, 23 Jan 2024 11:42:16 +0100 Subject: [PATCH 14/27] Update Readme --- README.md | 9 ++++++--- .../dev/gitlive/firebase/EncodersTest.kt | 2 +- .../kotlin/dev/gitlive/firebase/_encoders.kt | 1 - .../kotlin/dev/gitlive/firebase/_encoders.kt | 1 - .../firebase/firestore/GeoPointTests.kt | 6 +++++- .../firebase/firestore/TimestampTests.kt | 20 ++++++++++--------- .../gitlive/firebase/functions/functions.kt | 2 -- 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0a75adaaa..b01ff2f7d 100644 --- a/README.md +++ b/README.md @@ -85,13 +85,16 @@ data class City(val name: String) Instances of these classes can now be passed [along with their serializer](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#introduction-to-serializers) to the SDK: ```kotlin -db.collection("cities").document("LA").set(City.serializer(), city, encodeDefaults = true) +db.collection("cities").document("LA").set(City.serializer(), city) { shouldEncodeElementDefault = true } ``` -The `encodeDefaults` parameter is optional and defaults to `true`, set this to false to omit writing optional properties if they are equal to theirs default values. +The `buildSettings` closure is optional and allows for configuring serialization behaviour. + +Setting the `shouldEncodeElementDefault` parameter is optional and defaults to `true`, set this to false to omit writing optional properties if they are equal to theirs default values. Using [@EncodeDefault](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) on properties is a recommended way to locally override the behavior set with `encodeDefaults`. -You can also omit the serializer but this is discouraged due to a [current limitation on Kotlin/JS and Kotlin/Native](https://github.com/Kotlin/kotlinx.serialization/issues/1116#issuecomment-704342452) +You can also omit the serializer but this is discouraged due to a [current limitation on Kotlin/JS and Kotlin/Native](https://github.com/Kotlin/kotlinx.serialization/issues/1116#issuecomment-704342452). +To support [contextual serialization](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#contextual-serialization) or [open polymorphism](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism) the `serializersModule` can be overridden in any `EncodeSettings` or `DecodeSettings`

Server Timestamp

diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 99026654e..2dfde2804 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -124,7 +124,7 @@ class EncodersTest { @Test fun encodeDecodeSealedClass() { val sealedClass = SealedClass.Test("value") - val encoded = encode(SealedClass.serializer(), sealedClass, shouldEncodeElementDefault = true) + val encoded = encode(SealedClass.serializer(), sealedClass) { shouldEncodeElementDefault = true } nativeAssertEquals(nativeMapOf("type" to "test", "value" to "value"), encoded) diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt index 27dcb5820..144b0e60f 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -5,7 +5,6 @@ package dev.gitlive.firebase import kotlinx.serialization.descriptors.PolymorphicKind -import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index 299d52c68..dbb3d1f15 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -5,7 +5,6 @@ package dev.gitlive.firebase import kotlinx.serialization.descriptors.PolymorphicKind -import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.js.json 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 b6d2bcc26..a31348dd4 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 @@ -18,7 +18,11 @@ class GeoPointTests { fun encodeGeoPointObject() = runTest { val geoPoint = GeoPoint(12.3, 45.6) val item = TestDataWithGeoPoint("123", geoPoint) - val encoded = encodedAsMap(encode(item, shouldEncodeElementDefault = false)) + val encoded = encodedAsMap( + encode(item) { + shouldEncodeElementDefault = false + } + ) assertEquals("123", encoded["uid"]) // check GeoPoint is encoded to a platform representation assertEquals(geoPoint.nativeValue, encoded["location"]) 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 98292048a..be3011e90 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 @@ -7,9 +7,12 @@ import dev.gitlive.firebase.nativeAssertEquals import dev.gitlive.firebase.nativeMapOf import dev.gitlive.firebase.runTest import kotlinx.serialization.Serializable -import kotlin.test.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.DurationUnit @Serializable data class TestData( @@ -41,7 +44,7 @@ class TimestampTests { "updatedAt" to timestamp.nativeValue, "deletedAt" to null ), - encode(item, shouldEncodeElementDefault = false) + encode(item) { shouldEncodeElementDefault = false } ) } @@ -56,7 +59,7 @@ class TimestampTests { "updatedAt" to FieldValue.serverTimestamp.nativeValue, "deletedAt" to FieldValue.serverTimestamp.nativeValue ), - encode(item, shouldEncodeElementDefault = false) + encode(item) { shouldEncodeElementDefault = false } ) } @@ -104,11 +107,10 @@ class TimestampTests { @Test fun serializers() = runTest { - //todo dont work in js due to use of reified type in firebaseSerializer - uncomment once switched to IR -// assertEquals(BaseTimestampSerializer, (Timestamp(0, 0) as BaseTimestamp).firebaseSerializer()) -// assertEquals(BaseTimestampSerializer, (Timestamp.ServerTimestamp as BaseTimestamp).firebaseSerializer()) -// assertEquals(TimestampSerializer, Timestamp(0, 0).firebaseSerializer()) -// assertEquals(ServerTimestampSerializer, Timestamp.ServerTimestamp.firebaseSerializer()) + assertEquals(BaseTimestampSerializer, (Timestamp(0, 0) as BaseTimestamp).firebaseSerializer()) + assertEquals(BaseTimestampSerializer, (Timestamp.ServerTimestamp as BaseTimestamp).firebaseSerializer()) + assertEquals(TimestampSerializer, Timestamp(0, 0).firebaseSerializer()) + assertEquals(ServerTimestampSerializer, Timestamp.ServerTimestamp.firebaseSerializer()) } @Test 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 b051e721a..a2f62b55e 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 @@ -8,10 +8,8 @@ import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerializationStrategy import java.util.concurrent.TimeUnit actual val Firebase.functions From 59b4522e05bb57dbe83ac2317989bf1c2f21c14d Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sat, 27 Jan 2024 10:26:34 +0100 Subject: [PATCH 15/27] Some renames + readme update --- README.md | 35 +++++- .../gitlive/firebase/EncodeDecodeSettings.kt | 12 +- .../kotlin/dev/gitlive/firebase/encoders.kt | 14 +-- .../dev/gitlive/firebase/EncodersTest.kt | 47 ++++---- .../dev/gitlive/firebase/database/database.kt | 24 ++-- .../gitlive/firebase/firestore/firestore.kt | 104 +++++++++--------- .../firebase/firestore/GeoPointTests.kt | 2 +- .../firebase/firestore/TimestampTests.kt | 4 +- .../gitlive/firebase/firestore/firestore.kt | 8 +- .../gitlive/firebase/functions/functions.kt | 8 +- 10 files changed, 147 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index b01ff2f7d..30ece95bf 100644 --- a/README.md +++ b/README.md @@ -85,16 +85,43 @@ data class City(val name: String) Instances of these classes can now be passed [along with their serializer](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#introduction-to-serializers) to the SDK: ```kotlin -db.collection("cities").document("LA").set(City.serializer(), city) { shouldEncodeElementDefault = true } +db.collection("cities").document("LA").set(City.serializer(), city) { encodeDefaults = true } ``` The `buildSettings` closure is optional and allows for configuring serialization behaviour. -Setting the `shouldEncodeElementDefault` parameter is optional and defaults to `true`, set this to false to omit writing optional properties if they are equal to theirs default values. +Setting the `encodeDefaults` parameter is optional and defaults to `true`, set this to false to omit writing optional properties if they are equal to theirs default values. Using [@EncodeDefault](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) on properties is a recommended way to locally override the behavior set with `encodeDefaults`. -You can also omit the serializer but this is discouraged due to a [current limitation on Kotlin/JS and Kotlin/Native](https://github.com/Kotlin/kotlinx.serialization/issues/1116#issuecomment-704342452). -To support [contextual serialization](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#contextual-serialization) or [open polymorphism](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism) the `serializersModule` can be overridden in any `EncodeSettings` or `DecodeSettings` +You can also omit the serializer if it can be inferred using `serializer()`. +To support [contextual serialization](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#contextual-serialization) or [open polymorphism](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism) the `serializersModule` can be overridden in the `buildSettings` closure: + +```kotlin +@Serializable +abstract class AbstractCity { + abstract val name: String +} + +@Serializable +@SerialName("capital") +data class Capital(override val name: String, val isSeatOfGovernment: Boolean) : AbstractCity() + +val module = SerializersModule { + polymorphic(AbstractCity::class, AbstractCity.serializer()) { + subclass(Capital::class, Capital.serializer()) + } +} + +val city = Capital("London", true) +db.collection("cities").document("UK").set(AbstractCity.serializer(), city) { + encodeDefaults = true + serializersModule = module + +} +val storedCity = db.collection("cities").document("UK").get().data(AbstractCity.serializer()) { + serializersModule = module +} +```

Server Timestamp

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 dc12f3b59..076f208dc 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt @@ -16,23 +16,23 @@ sealed class EncodeDecodeSettings { /** * [EncodeDecodeSettings] used when encoding an object - * @property shouldEncodeElementDefault if `true` this will explicitly encode elements even if they are their default value + * @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 shouldEncodeElementDefault: Boolean, + val encodeDefaults: Boolean, override val serializersModule: SerializersModule, ) : EncodeDecodeSettings() { interface Builder { - var shouldEncodeElementDefault: Boolean + var encodeDefaults: Boolean var serializersModule: SerializersModule } @PublishedApi internal class BuilderImpl : Builder { - override var shouldEncodeElementDefault: Boolean = true + override var encodeDefaults: Boolean = true override var serializersModule: SerializersModule = EmptySerializersModule() } } @@ -60,11 +60,11 @@ interface EncodeDecodeSettingsBuilder : EncodeSettings.Builder, DecodeSettings.B @PublishedApi internal class EncodeDecodeSettingsBuilderImpl : EncodeDecodeSettingsBuilder { - override var shouldEncodeElementDefault: Boolean = true + override var encodeDefaults: Boolean = true override var serializersModule: SerializersModule = EmptySerializersModule() } @PublishedApi -internal fun EncodeSettings.Builder.buildEncodeSettings(): EncodeSettings = EncodeSettings(shouldEncodeElementDefault, serializersModule) +internal fun EncodeSettings.Builder.buildEncodeSettings(): EncodeSettings = EncodeSettings(encodeDefaults, serializersModule) @PublishedApi internal fun DecodeSettings.Builder.buildDecodeSettings(): DecodeSettings = DecodeSettings(serializersModule) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 33178187c..978072f90 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -11,9 +11,9 @@ 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) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }")) +@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.shouldEncodeElementDefault = shouldEncodeElementDefault + this.encodeDefaults = shouldEncodeElementDefault } inline fun encode(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit) = @@ -23,9 +23,9 @@ inline fun encode(strategy: SerializationStrategy, value: T, buildSetting internal inline fun encode(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings): Any? = FirebaseEncoder(encodeSettings).apply { encodeSerializableValue(strategy, value) }.value -@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(value) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }")) +@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(value) { this.encodeDefaults = shouldEncodeElementDefault }")) inline fun encode(value: T, shouldEncodeElementDefault: Boolean): Any? = encode(value) { - this.shouldEncodeElementDefault = shouldEncodeElementDefault + this.encodeDefaults = shouldEncodeElementDefault } inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = @@ -60,12 +60,12 @@ class FirebaseEncoder( ) : Encoder { constructor(shouldEncodeElementDefault: Boolean) : this( - EncodeSettings.BuilderImpl().apply { this.shouldEncodeElementDefault = shouldEncodeElementDefault }.buildEncodeSettings() + EncodeSettings.BuilderImpl().apply { this.encodeDefaults = shouldEncodeElementDefault }.buildEncodeSettings() ) var value: Any? = null - internal val shouldEncodeElementDefault = settings.shouldEncodeElementDefault + internal val shouldEncodeElementDefault = settings.encodeDefaults override val serializersModule: SerializersModule = settings.serializersModule private var polymorphicDiscriminator: String? = null @@ -154,7 +154,7 @@ open class FirebaseCompositeEncoder constructor( override fun endStructure(descriptor: SerialDescriptor) = end() - override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int) = settings.shouldEncodeElementDefault + override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int) = settings.encodeDefaults override fun encodeNullableSerializableElement( descriptor: SerialDescriptor, diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 2dfde2804..4f2e3ced3 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -10,6 +10,7 @@ import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic import kotlin.test.Test import kotlin.test.assertEquals @@ -59,7 +60,7 @@ class EncodersTest { @Test fun encodeDecodeList() { val list = listOf("One", "Two", "Three") - val encoded = encode>(list) { shouldEncodeElementDefault = true } + val encoded = encode>(list) { encodeDefaults = true } nativeAssertEquals(nativeListOf("One", "Two", "Three"), encoded) @@ -70,7 +71,7 @@ class EncodersTest { @Test fun encodeDecodeMap() { val map = mapOf("key" to "value", "key2" to "value2", "key3" to "value3") - val encoded = encode>(map) { shouldEncodeElementDefault = true } + val encoded = encode>(map) { encodeDefaults = true } nativeAssertEquals(nativeMapOf("key" to "value", "key2" to "value2", "key3" to "value3"), encoded) @@ -80,7 +81,7 @@ class EncodersTest { @Test fun encodeDecodeObject() { - val encoded = encode(TestObject.serializer(), TestObject) { shouldEncodeElementDefault = false } + val encoded = encode(TestObject.serializer(), TestObject) { encodeDefaults = false } nativeAssertEquals(nativeMapOf(), encoded) val decoded = decode(TestObject.serializer(), encoded) @@ -90,7 +91,7 @@ class EncodersTest { @Test fun encodeDecodeClass() { val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) - val encoded = encode(TestData.serializer(), testDataClass) { shouldEncodeElementDefault = false } + val encoded = encode(TestData.serializer(), testDataClass) { encodeDefaults = false } nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true), encoded) @@ -101,7 +102,7 @@ class EncodersTest { @Test fun encodeDecodeClassNullableValue() { val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, nullableBool = true) - val encoded = encode(TestData.serializer(), testDataClass) { shouldEncodeElementDefault = true } + val encoded = encode(TestData.serializer(), testDataClass) { encodeDefaults = true } nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), encoded) @@ -113,7 +114,7 @@ class EncodersTest { fun encodeDecodeGenericClass() { val innerClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) val genericClass = GenericClass(innerClass) - val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass) { shouldEncodeElementDefault = true } + val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass) { encodeDefaults = true } nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null)), encoded) @@ -124,7 +125,7 @@ class EncodersTest { @Test fun encodeDecodeSealedClass() { val sealedClass = SealedClass.Test("value") - val encoded = encode(SealedClass.serializer(), sealedClass) { shouldEncodeElementDefault = true } + val encoded = encode(SealedClass.serializer(), sealedClass) { encodeDefaults = true } nativeAssertEquals(nativeMapOf("type" to "test", "value" to "value"), encoded) @@ -135,12 +136,14 @@ class EncodersTest { @Test fun encodeDecodePolymorphicClass() { val module = SerializersModule { - polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) + polymorphic(AbstractClass::class, AbstractClass.serializer()) { + subclass(ImplementedClass::class, ImplementedClass.serializer()) + } } val abstractClass: AbstractClass = ImplementedClass("value", true) val encoded = encode(AbstractClass.serializer(), abstractClass) { - shouldEncodeElementDefault = true + encodeDefaults = true serializersModule = module } @@ -155,14 +158,16 @@ class EncodersTest { @Test fun encodeDecodeNestedClass() { val module = SerializersModule { - polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) + polymorphic(AbstractClass::class, AbstractClass.serializer()) { + subclass(ImplementedClass::class, ImplementedClass.serializer()) + } } val sealedClass: SealedClass = SealedClass.Test("value") val abstractClass: AbstractClass = ImplementedClass("value", true) val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) val encoded = encode(NestedClass.serializer(), nestedClass) { - shouldEncodeElementDefault = true + encodeDefaults = true serializersModule = module } @@ -207,7 +212,7 @@ class EncodersTest { @Test fun reencodeTransformationObject() { - val reencoded = reencodeTransformation(nativeMapOf(), { shouldEncodeElementDefault = false }) { + val reencoded = reencodeTransformation(nativeMapOf(), { encodeDefaults = false }) { assertEquals(TestObject, it) it } @@ -218,7 +223,7 @@ class EncodersTest { fun reencodeTransformationClass() { val reencoded = reencodeTransformation( nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), - { shouldEncodeElementDefault = false } + { encodeDefaults = false } ) { assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true), it) it.copy(map = mapOf("newKey" to "newValue"), nullableBool = null) @@ -231,7 +236,7 @@ class EncodersTest { fun reencodeTransformationNullableValue() { val reencoded = reencodeTransformation( nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), - { shouldEncodeElementDefault = false } + { encodeDefaults = false } ) { assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true), it) null @@ -245,7 +250,7 @@ class EncodersTest { val reencoded = reencodeTransformation( GenericClass.serializer(TestData.serializer()), nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to false)), - { shouldEncodeElementDefault = false } + { encodeDefaults = false } ) { assertEquals( GenericClass(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = false)), @@ -270,7 +275,9 @@ class EncodersTest { @Test fun reencodeTransformationPolymorphicClass() { val module = SerializersModule { - polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) + polymorphic(AbstractClass::class, AbstractClass.serializer()) { + subclass(ImplementedClass::class, ImplementedClass.serializer()) + } } val reencoded = reencodeTransformation( @@ -290,19 +297,21 @@ class EncodersTest { @Test fun reencodeTransformationNestedClass() { val module = SerializersModule { - polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) + polymorphic(AbstractClass::class, AbstractClass.serializer()) { + subclass(ImplementedClass::class, ImplementedClass.serializer()) + } } val sealedClass: SealedClass = SealedClass.Test("value") val abstractClass: AbstractClass = ImplementedClass("value", true) val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) val encoded = encode(NestedClass.serializer(), nestedClass) { - shouldEncodeElementDefault = true + encodeDefaults = true serializersModule = module } val reencoded = reencodeTransformation(NestedClass.serializer(), encoded, builder = { - shouldEncodeElementDefault = true + encodeDefaults = true serializersModule = module }) { assertEquals(nestedClass, it) 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 27fa9feba..5f319de76 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 @@ -75,27 +75,27 @@ expect open class Query internal constructor(nativeQuery: NativeQuery) { abstract class BaseDatabaseReference internal constructor(nativeQuery: NativeQuery) : Query(nativeQuery) { - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { this.encodeDefaults = encodeDefaults }")) suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = setValue(value) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValueEncoded(encode(value, buildSettings)) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { this.encodeDefaults = encodeDefaults }")) suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = setValue(strategy, value) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValueEncoded(encode(strategy, value, buildSettings)) @PublishedApi internal abstract suspend fun setValueEncoded(encodedValue: Any?) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { this.encodeDefaults = encodeDefaults }")) suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedChildren( encode(update, buildSettings)) @@ -130,23 +130,23 @@ expect class DataSnapshot { expect class DatabaseException(message: String?, cause: Throwable?) : RuntimeException abstract class BaseOnDisconnect internal constructor() { - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { this.encodeDefaults = encodeDefaults }")) suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = - setValue(value) { shouldEncodeElementDefault = encodeDefaults } + setValue(value) { this.encodeDefaults = encodeDefaults } suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValue(encode(value, buildSettings)) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { this.encodeDefaults = encodeDefaults }")) suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = - setValue(strategy, value) { shouldEncodeElementDefault = encodeDefaults } + setValue(strategy, value) { this.encodeDefaults = encodeDefaults } suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValue(encode(strategy, value, buildSettings)) @PublishedApi internal abstract suspend fun setValue(encodedValue: Any?) suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedChildren(update.mapValues { (_, it) -> encode(it, buildSettings) }) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { this.encodeDefaults = encodeDefaults }")) suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = updateChildren(update) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } @PublishedApi 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 8b774c02e..143920a7e 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -42,54 +42,54 @@ sealed class SetOptions { abstract class BaseTransaction { - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + @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): BaseTransaction = set(documentRef, data, merge) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun set(documentRef: DocumentReference, data: Any, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseTransaction - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { this.encodeDefaults = encodeDefaults }")) fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean) = update(documentRef, data) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun update(documentRef: DocumentReference, data: Any, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encode(data, buildSettings)!!) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encode(strategy, data, buildSettings)!!) @@ -215,44 +215,44 @@ internal val Any.safeValue: Any get() = when (this) { abstract class BaseWriteBatch { - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + 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) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + 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())) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + 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())) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + 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) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + @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){ - shouldEncodeElementDefault = encodeDefaults + 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())) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + 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())) @@ -260,16 +260,16 @@ abstract class BaseWriteBatch { @PublishedApi internal abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseWriteBatch - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun update(documentRef: DocumentReference, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encode(data, buildSettings)!!) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(documentRef, encode(strategy, data, buildSettings)!!) @@ -300,41 +300,41 @@ expect class NativeDocumentReference abstract class BaseDocumentReference { - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, merge) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( encode(strategy, data, buildSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFields) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( encode(strategy, data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFieldPaths) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @@ -342,15 +342,15 @@ abstract class BaseDocumentReference { @PublishedApi internal abstract suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(data) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(data) { this.encodeDefaults = encodeDefaults }")) suspend inline fun update(data: T, encodeDefaults: Boolean) = update(data) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(encode(data, buildSettings)!!) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(encode(strategy, data, buildSettings)!!) @@ -389,17 +389,17 @@ expect class DocumentReference internal constructor(nativeValue: NativeDocumentR abstract class BaseCollectionReference(nativeQuery: NativeQuery) : Query(nativeQuery) { - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(data) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(data) { this.encodeDefaults = encodeDefaults }")) suspend inline fun add(data: T, encodeDefaults: Boolean) = add(data) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun add(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( encode(data, buildSettings)!! ) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + @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) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline fun add(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = addEncoded( encode(strategy, data, buildSettings)!! 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 a31348dd4..216621064 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 @@ -20,7 +20,7 @@ class GeoPointTests { val item = TestDataWithGeoPoint("123", geoPoint) val encoded = encodedAsMap( encode(item) { - shouldEncodeElementDefault = false + encodeDefaults = false } ) assertEquals("123", encoded["uid"]) 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 be3011e90..8c2541ba9 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 @@ -44,7 +44,7 @@ class TimestampTests { "updatedAt" to timestamp.nativeValue, "deletedAt" to null ), - encode(item) { shouldEncodeElementDefault = false } + encode(item) { encodeDefaults = false } ) } @@ -59,7 +59,7 @@ class TimestampTests { "updatedAt" to FieldValue.serverTimestamp.nativeValue, "deletedAt" to FieldValue.serverTimestamp.nativeValue ), - encode(item) { shouldEncodeElementDefault = false } + encode(item) { encodeDefaults = false } ) } 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 2fe196e66..f59c0b2c2 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 @@ -541,7 +541,7 @@ class FirebaseFirestoreTest { time = 123.0 ), ) { - shouldEncodeElementDefault = false + encodeDefaults = false } batch.commit() @@ -569,7 +569,7 @@ class FirebaseFirestoreTest { time = 126.0 ), ) { - shouldEncodeElementDefault = false + encodeDefaults = false } batch.commit() @@ -734,7 +734,7 @@ class FirebaseFirestoreTest { val item = TestDataWithDocumentReference("123", doc, doc) val encoded = encodedAsMap( encode(item) { - shouldEncodeElementDefault = false + encodeDefaults = false } ) assertEquals("123", encoded["uid"]) @@ -747,7 +747,7 @@ class FirebaseFirestoreTest { val item = TestDataWithOptionalDocumentReference(null) val encoded = encodedAsMap( encode(item) { - shouldEncodeElementDefault = false + encodeDefaults = false } ) assertNull(encoded["optionalReference"]) 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 91a9c3226..1ec19d65d 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 @@ -14,15 +14,15 @@ expect class FirebaseFunctions { } abstract class BaseHttpsCallableReference { - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(data) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(data) { this.encodeDefaults = encodeDefaults }")) suspend inline operator fun invoke(data: T, encodeDefaults: Boolean) = invoke(data) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline operator fun invoke(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = invoke(encode(data, buildSettings)!!) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(strategy, data) { shouldEncodeElementDefault = encodeDefaults }")) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(strategy, data) { this.encodeDefaults = encodeDefaults }")) suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): HttpsCallableResult = invoke(strategy, data) { - shouldEncodeElementDefault = encodeDefaults + this.encodeDefaults = encodeDefaults } suspend inline operator fun invoke(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = invoke(encode(strategy, data, buildSettings)!!) From 7a2ae5d2030cd8ecee4c0ac4956da1ea858014d0 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sat, 27 Jan 2024 10:33:09 +0100 Subject: [PATCH 16/27] Add value class fix --- .../kotlin/dev/gitlive/firebase/encoders.kt | 3 +- .../dev/gitlive/firebase/EncodersTest.kt | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 978072f90..0497ec0d7 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -127,8 +127,7 @@ class FirebaseEncoder( this.value = value } - override fun encodeInline(descriptor: SerialDescriptor): Encoder = - FirebaseEncoder(settings) + override fun encodeInline(descriptor: SerialDescriptor): Encoder = this override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { encodePolymorphically(serializer, value) { diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 4f2e3ced3..49990757d 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -11,6 +11,7 @@ import kotlinx.serialization.builtins.MapSerializer 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 @@ -55,6 +56,13 @@ data class NestedClass( val abstractMap: Map ) +@Serializable +@JvmInline +value class ValueClass(val int: Int) + +@Serializable +data class ValueClassWrapper(val value: ValueClass) + class EncodersTest { @Test @@ -191,6 +199,28 @@ class EncodersTest { assertEquals(nestedClass, decoded) } + @Test + fun encodeDecodeValueClassWrapper() { + val testValueClassWrapper = ValueClassWrapper(ValueClass(42)) + val encoded = encode(ValueClassWrapper.serializer(), testValueClassWrapper) { encodeDefaults = false } + + nativeAssertEquals(nativeMapOf("value" to 42), encoded) + + val decoded = decode(ValueClassWrapper.serializer(), encoded) + assertEquals(testValueClassWrapper, decoded) + } + + @Test + fun encodeDecodeValueClass() { + val testValueClass = ValueClass(42) + val encoded = encode(ValueClass.serializer(), testValueClass) { encodeDefaults = false } + + nativeAssertEquals(42, encoded) + + val decoded = decode(ValueClass.serializer(), encoded) + assertEquals(testValueClass, decoded) + } + @Test fun reencodeTransformationList() { val reencoded = reencodeTransformation>(nativeListOf("One", "Two", "Three")) { From 2fb7f5853790cfaf0b9d9a4b148741561e7f3bdb Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sat, 27 Jan 2024 10:48:59 +0100 Subject: [PATCH 17/27] Made helper methods internal --- .../kotlin/dev/gitlive/firebase/firestore/helpers.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt index 147239e5f..bde2fd6bf 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt @@ -5,20 +5,23 @@ import kotlin.jvm.JvmName //** Helper method to perform an update operation. */ @JvmName("performUpdateFields") -inline fun encodeFieldAndValue( +@PublishedApi +internal inline fun encodeFieldAndValue( fieldsAndValues: Array>, buildSettings: EncodeSettings.Builder.() -> Unit, ) = encodeFieldAndValue(fieldsAndValues, encodeField = { it }, encodeValue = { encode(it, buildSettings) }) /** Helper method to perform an update operation. */ @JvmName("performUpdateFieldPaths") -inline fun encodeFieldAndValue( +@PublishedApi +internal inline fun encodeFieldAndValue( fieldsAndValues: Array>, buildSettings: EncodeSettings.Builder.() -> Unit, ) = encodeFieldAndValue(fieldsAndValues, { it.encoded }, { encode(it, buildSettings) }) /** Helper method to perform an update operation in Android and JS. */ -inline fun encodeFieldAndValue( +@PublishedApi +internal inline fun encodeFieldAndValue( fieldsAndValues: Array>, encodeField: (T) -> K, encodeValue: (Any?) -> Any? From 0ca5675a14fde70112668328abdb76bff08211e9 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sat, 27 Jan 2024 12:22:52 +0100 Subject: [PATCH 18/27] Slight optimization of Value class tests --- .../dev/gitlive/firebase/EncodersTest.kt | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 49990757d..7afa25ae9 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -23,7 +23,17 @@ object TestObject { } @Serializable -data class TestData(val map: Map, val otherMap: Map, val bool: Boolean = false, val nullableBool: Boolean? = null) +@JvmInline +value class ValueClass(val int: Int) + +@Serializable +data class TestData( + val map: Map, + val otherMap: Map, + val bool: Boolean = false, + val nullableBool: Boolean? = null, + val valueClass: ValueClass, +) @Serializable sealed class SealedClass { @@ -56,13 +66,6 @@ data class NestedClass( val abstractMap: Map ) -@Serializable -@JvmInline -value class ValueClass(val int: Int) - -@Serializable -data class ValueClassWrapper(val value: ValueClass) - class EncodersTest { @Test @@ -96,12 +99,23 @@ class EncodersTest { assertEquals(TestObject, decoded) } + @Test + fun encodeDecodeValueClass() { + val testValueClass = ValueClass(42) + val encoded = encode(ValueClass.serializer(), testValueClass) { encodeDefaults = false } + + nativeAssertEquals(42, encoded) + + val decoded = decode(ValueClass.serializer(), encoded) + assertEquals(testValueClass, decoded) + } + @Test fun encodeDecodeClass() { - val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) + val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) val encoded = encode(TestData.serializer(), testDataClass) { encodeDefaults = false } - nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true), encoded) + nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), encoded) val decoded = decode(TestData.serializer(), encoded) assertEquals(testDataClass, decoded) @@ -109,10 +123,10 @@ class EncodersTest { @Test fun encodeDecodeClassNullableValue() { - val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, nullableBool = true) + val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, nullableBool = true, ValueClass(42)) val encoded = encode(TestData.serializer(), testDataClass) { encodeDefaults = true } - nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), encoded) + nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true, "valueClass" to 42), encoded) val decoded = decode(TestData.serializer(), encoded) assertEquals(testDataClass, decoded) @@ -120,11 +134,11 @@ class EncodersTest { @Test fun encodeDecodeGenericClass() { - val innerClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) + val innerClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, valueClass = ValueClass(42)) val genericClass = GenericClass(innerClass) val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass) { encodeDefaults = true } - nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null)), encoded) + nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null, "valueClass" to 42)), encoded) val decoded = decode(GenericClass.serializer(TestData.serializer()), encoded) assertEquals(genericClass, decoded) @@ -199,28 +213,6 @@ class EncodersTest { assertEquals(nestedClass, decoded) } - @Test - fun encodeDecodeValueClassWrapper() { - val testValueClassWrapper = ValueClassWrapper(ValueClass(42)) - val encoded = encode(ValueClassWrapper.serializer(), testValueClassWrapper) { encodeDefaults = false } - - nativeAssertEquals(nativeMapOf("value" to 42), encoded) - - val decoded = decode(ValueClassWrapper.serializer(), encoded) - assertEquals(testValueClassWrapper, decoded) - } - - @Test - fun encodeDecodeValueClass() { - val testValueClass = ValueClass(42) - val encoded = encode(ValueClass.serializer(), testValueClass) { encodeDefaults = false } - - nativeAssertEquals(42, encoded) - - val decoded = decode(ValueClass.serializer(), encoded) - assertEquals(testValueClass, decoded) - } - @Test fun reencodeTransformationList() { val reencoded = reencodeTransformation>(nativeListOf("One", "Two", "Three")) { @@ -249,26 +241,39 @@ class EncodersTest { nativeAssertEquals(nativeMapOf(), reencoded) } + @Test + fun reencodeTransformationValueClass() { + val reencoded = reencodeTransformation( + 42, + { encodeDefaults = false } + ) { + assertEquals(ValueClass(42), it) + ValueClass(23) + } + + nativeAssertEquals(23, reencoded) + } + @Test fun reencodeTransformationClass() { val reencoded = reencodeTransformation( - nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), + nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true, "valueClass" to 42), { encodeDefaults = false } ) { - assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true), it) + assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true, ValueClass(42)), it) it.copy(map = mapOf("newKey" to "newValue"), nullableBool = null) } - nativeAssertEquals(nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true), reencoded) + nativeAssertEquals(nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), reencoded) } @Test fun reencodeTransformationNullableValue() { val reencoded = reencodeTransformation( - nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), + nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true, "valueClass" to 42), { encodeDefaults = false } ) { - assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true), it) + assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true, valueClass = ValueClass(42)), it) null } @@ -279,17 +284,17 @@ class EncodersTest { fun reencodeTransformationGenericClass() { val reencoded = reencodeTransformation( GenericClass.serializer(TestData.serializer()), - nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to false)), + nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to false, "valueClass" to 42)), { encodeDefaults = false } ) { assertEquals( - GenericClass(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = false)), + GenericClass(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = false, valueClass = ValueClass(42))), it ) GenericClass(it.inner.copy(map = mapOf("newKey" to "newValue"), nullableBool = null)) } - nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true)), reencoded) + nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42)), reencoded) } @Test From 19950712636846b6f7ce2f24839f6b2730335ecf Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 29 Jan 2024 11:38:35 +0100 Subject: [PATCH 19/27] Small test improvement --- .../kotlin/dev/gitlive/firebase/Polymorphic.kt | 5 ++--- .../kotlin/dev/gitlive/firebase/decoders.kt | 6 +----- .../kotlin/dev/gitlive/firebase/EncodersTest.kt | 11 +++++++++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt index 979415c14..41563d527 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt @@ -16,7 +16,7 @@ internal fun FirebaseEncoder.encodePolymorphically( value: T, ifPolymorphic: (String) -> Unit ) { - // If serializer is not an AbstractPolymorphicSerializer or if we are encoding this as a list, we can just use the regular serializer + // If serializer is not an AbstractPolymorphicSerializer we can just use the regular serializer // This will result in calling structureEncoder for complicated structures // For PolymorphicKind this will first encode the polymorphic discriminator as a String and the remaining StructureKind.Class as a map of key-value pairs // This will result in a list structured like: (type, { classKey = classValue }) @@ -25,7 +25,6 @@ internal fun FirebaseEncoder.encodePolymorphically( return } - // When doing Polymorphic Serialization with EncodeDecodeSettings.PolymorphicStructure.MAP we will use the polymorphic serializer of the class. val casted = serializer as AbstractPolymorphicSerializer val baseClassDiscriminator = serializer.descriptor.classDiscriminator() val actualSerializer = casted.findPolymorphicSerializer(this, value as Any) @@ -38,7 +37,7 @@ internal fun FirebaseDecoder.decodeSerializableValuePolymorphic( value: Any?, deserializer: DeserializationStrategy, ): T { - // If deserializer is not an AbstractPolymorphicSerializer or if we are decoding this from a list, we can just use the regular serializer + // If deserializer is not an AbstractPolymorphicSerializer we can just use the regular serializer if (deserializer !is AbstractPolymorphicSerializer<*>) { return deserializer.deserialize(this) } diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt index 08129e9af..3822399d2 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt @@ -87,11 +87,7 @@ class FirebaseClassDecoder( override fun decodeElementIndex(descriptor: SerialDescriptor): Int { return (index until descriptor.elementsCount) .firstOrNull { - !descriptor.isElementOptional(it) || containsKey( - descriptor.getElementName( - it - ) - ) + !descriptor.isElementOptional(it) || containsKey(descriptor.getElementName(it)) } ?.also { index = it + 1 } ?: DECODE_DONE diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 7afa25ae9..c68403052 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -58,6 +58,7 @@ data class ImplementedClass(override val value: String, val otherValue: Boolean) @Serializable data class NestedClass( + val testData: TestData, val sealed: SealedClass, val abstract: AbstractClass, val sealedList: List, @@ -185,18 +186,21 @@ class EncodersTest { } } + val testData = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) val sealedClass: SealedClass = SealedClass.Test("value") val abstractClass: AbstractClass = ImplementedClass("value", true) - val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) + val nestedClass = NestedClass(testData, sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) val encoded = encode(NestedClass.serializer(), nestedClass) { encodeDefaults = true serializersModule = module } + val testDataEncoded = nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null, "valueClass" to 42) val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") val abstractEncoded = nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true) nativeAssertEquals( nativeMapOf( + "testData" to testDataEncoded, "sealed" to sealedEncoded, "abstract" to abstractEncoded, "sealedList" to nativeListOf(sealedEncoded), @@ -337,9 +341,10 @@ class EncodersTest { } } + val testData = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) val sealedClass: SealedClass = SealedClass.Test("value") val abstractClass: AbstractClass = ImplementedClass("value", true) - val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) + val nestedClass = NestedClass(testData, sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) val encoded = encode(NestedClass.serializer(), nestedClass) { encodeDefaults = true serializersModule = module @@ -353,10 +358,12 @@ class EncodersTest { it.copy(sealed = SealedClass.Test("newValue")) } + val testDataEncoded = nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null, "valueClass" to 42) val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") val abstractEncoded = nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true) nativeAssertEquals( nativeMapOf( + "testData" to testDataEncoded, "sealed" to nativeMapOf("type" to "test", "value" to "newValue"), "abstract" to abstractEncoded, "sealedList" to nativeListOf(sealedEncoded), From 87d4ae26e8e005a1293aacc7cf6e4647b9a6fc13 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 29 Jan 2024 12:16:52 +0100 Subject: [PATCH 20/27] Decode polymorphic using elementName rather than index More in line with old code and probably more stable since we're dealing with maps --- .../kotlin/dev/gitlive/firebase/_decoders.kt | 6 +++--- .../kotlin/dev/gitlive/firebase/EncodersTest.kt | 16 ++++++++-------- .../kotlin/dev/gitlive/firebase/_decoders.kt | 6 +++--- .../kotlin/dev/gitlive/firebase/_decoders.kt | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt index 5a33fa84c..ae930a08a 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt @@ -32,10 +32,10 @@ actual fun getPolymorphicType(value: Any?, discriminator: String): String = private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map -> FirebaseClassDecoder(map.size, settings, { map.containsKey(it) }) { desc, index -> if (isNestedPolymorphic) { - if (index == 0) - map[desc.getElementName(index)] - else { + if (desc.getElementName(index) == "value") map + else { + map[desc.getElementName(index)] } } else { map[desc.getElementName(index)] diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index c68403052..59340a310 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -49,12 +49,12 @@ data class GenericClass( @Serializable abstract class AbstractClass { - abstract val value: String + abstract val abstractValue: String } @Serializable @SerialName("implemented") -data class ImplementedClass(override val value: String, val otherValue: Boolean) : AbstractClass() +data class ImplementedClass(override val abstractValue: String, val otherValue: Boolean) : AbstractClass() @Serializable data class NestedClass( @@ -170,7 +170,7 @@ class EncodersTest { serializersModule = module } - nativeAssertEquals(nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true), encoded) + nativeAssertEquals(nativeMapOf("type" to "implemented", "abstractValue" to "value", "otherValue" to true), encoded) val decoded = decode(AbstractClass.serializer(), encoded) { serializersModule = module @@ -197,7 +197,7 @@ class EncodersTest { val testDataEncoded = nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null, "valueClass" to 42) val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") - val abstractEncoded = nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true) + val abstractEncoded = nativeMapOf("type" to "implemented", "abstractValue" to "value", "otherValue" to true) nativeAssertEquals( nativeMapOf( "testData" to testDataEncoded, @@ -321,16 +321,16 @@ class EncodersTest { val reencoded = reencodeTransformation( AbstractClass.serializer(), - nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true), + nativeMapOf("type" to "implemented", "abstractValue" to "value", "otherValue" to true), builder = { serializersModule = module } ) { assertEquals(ImplementedClass("value", true), it) - ImplementedClass("new-${it.value}", false) + ImplementedClass("new-${it.abstractValue}", false) } - nativeAssertEquals(nativeMapOf("type" to "implemented", "value" to "new-value", "otherValue" to false), reencoded) + nativeAssertEquals(nativeMapOf("type" to "implemented", "abstractValue" to "new-value", "otherValue" to false), reencoded) } @Test @@ -360,7 +360,7 @@ class EncodersTest { val testDataEncoded = nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null, "valueClass" to 42) val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") - val abstractEncoded = nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true) + val abstractEncoded = nativeMapOf("type" to "implemented", "abstractValue" to "value", "otherValue" to true) nativeAssertEquals( nativeMapOf( "testData" to testDataEncoded, diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt index 6576c3ada..43589a7a9 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt @@ -28,10 +28,10 @@ private fun FirebaseDecoder.decodeAsList(): CompositeDecoder = (value as? List<* private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map -> FirebaseClassDecoder(map.size, settings, { map.containsKey(it) }) { desc, index -> if (isNestedPolymorphic) { - if (index == 0) - map[desc.getElementName(index)] - else { + if (desc.getElementName(index) == "value") map + else { + map[desc.getElementName(index)] } } else { map[desc.getElementName(index)] diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt index 37dc76017..a849dd190 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt @@ -43,10 +43,10 @@ private fun FirebaseDecoder.decodeAsList(): CompositeDecoder = (value as Array<* private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as Json).let { json -> FirebaseClassDecoder(js("Object").keys(value).length as Int, settings, { json[it] != undefined }) { desc, index -> if (isNestedPolymorphic) { - if (index == 0) { - json[desc.getElementName(index)] - } else { + if (desc.getElementName(index) == "value") { json + } else { + json[desc.getElementName(index)] } } else { json[desc.getElementName(index)] From 71bcce44c42a3858c9300b6d9792fe50adf1b622 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 29 Jan 2024 12:27:27 +0100 Subject: [PATCH 21/27] Also test nested data class encoding --- .../kotlin/dev/gitlive/firebase/EncodersTest.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt index 59340a310..5fdeaf72c 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -61,8 +61,10 @@ data class NestedClass( val testData: TestData, val sealed: SealedClass, val abstract: AbstractClass, + val testDataList: List, val sealedList: List, val abstractList: List, + val testDataMap: Map, val sealedMap: Map, val abstractMap: Map ) @@ -189,7 +191,7 @@ class EncodersTest { val testData = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) val sealedClass: SealedClass = SealedClass.Test("value") val abstractClass: AbstractClass = ImplementedClass("value", true) - val nestedClass = NestedClass(testData, sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) + val nestedClass = NestedClass(testData, sealedClass, abstractClass, listOf(testData), listOf(sealedClass), listOf(abstractClass), mapOf(testData to testData), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) val encoded = encode(NestedClass.serializer(), nestedClass) { encodeDefaults = true serializersModule = module @@ -203,8 +205,10 @@ class EncodersTest { "testData" to testDataEncoded, "sealed" to sealedEncoded, "abstract" to abstractEncoded, + "testDataList" to nativeListOf(testDataEncoded), "sealedList" to nativeListOf(sealedEncoded), "abstractList" to nativeListOf(abstractEncoded), + "testDataMap" to nativeMapOf(testDataEncoded to testDataEncoded), "sealedMap" to nativeMapOf(sealedEncoded to sealedEncoded), "abstractMap" to nativeMapOf(abstractEncoded to abstractEncoded) ), @@ -344,7 +348,7 @@ class EncodersTest { val testData = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) val sealedClass: SealedClass = SealedClass.Test("value") val abstractClass: AbstractClass = ImplementedClass("value", true) - val nestedClass = NestedClass(testData, sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) + val nestedClass = NestedClass(testData, sealedClass, abstractClass, listOf(testData), listOf(sealedClass), listOf(abstractClass), mapOf(testData to testData), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) val encoded = encode(NestedClass.serializer(), nestedClass) { encodeDefaults = true serializersModule = module @@ -366,8 +370,10 @@ class EncodersTest { "testData" to testDataEncoded, "sealed" to nativeMapOf("type" to "test", "value" to "newValue"), "abstract" to abstractEncoded, + "testDataList" to nativeListOf(testDataEncoded), "sealedList" to nativeListOf(sealedEncoded), "abstractList" to nativeListOf(abstractEncoded), + "testDataMap" to nativeMapOf(testDataEncoded to testDataEncoded), "sealedMap" to nativeMapOf(sealedEncoded to sealedEncoded), "abstractMap" to nativeMapOf(abstractEncoded to abstractEncoded) ), From ef92b68dec0afbbd4ed89db5ad0f7c0fff8e6508 Mon Sep 17 00:00:00 2001 From: nbransby Date: Mon, 26 Feb 2024 12:16:18 +0800 Subject: [PATCH 22/27] add initializeFirestore to firestore js externals --- .../gitlive/firebase/firestore/externals/firestore.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt index 7f5065a40..b04056e8e 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt @@ -73,6 +73,8 @@ external fun getDocs(query: Query): Promise external fun getFirestore(app: FirebaseApp? = definedExternally): Firestore +external fun initializeFirestore(app: FirebaseApp? = definedExternally, settings: dynamic = definedExternally, databaseId: String? = definedExternally): Firestore + external fun increment(n: Int): FieldValue external fun initializeFirestore(app: FirebaseApp, settings: Any): Firestore @@ -287,3 +289,10 @@ external class Timestamp(seconds: Double, nanoseconds: Double) { fun isEqual(other: Timestamp): Boolean } + +external interface FirestoreLocalCache { + val kind: String +} + +external fun memoryLocalCache(): FirestoreLocalCache +external fun persistentLocalCache(settings: dynamic = definedExternally): FirestoreLocalCache From d13f931245a8bfcf390c4cbf5b271f2c1d9aeaa6 Mon Sep 17 00:00:00 2001 From: nbransby Date: Mon, 26 Feb 2024 17:58:20 +0800 Subject: [PATCH 23/27] remove dup initializeFirestore --- .../dev/gitlive/firebase/firestore/externals/firestore.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt index b04056e8e..1837975c2 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt @@ -73,11 +73,9 @@ external fun getDocs(query: Query): Promise external fun getFirestore(app: FirebaseApp? = definedExternally): Firestore -external fun initializeFirestore(app: FirebaseApp? = definedExternally, settings: dynamic = definedExternally, databaseId: String? = definedExternally): Firestore - external fun increment(n: Int): FieldValue -external fun initializeFirestore(app: FirebaseApp, settings: Any): Firestore +external fun initializeFirestore(app: FirebaseApp, settings: dynamic = definedExternally, databaseId: String? = definedExternally): Firestore external fun limit(limit: Number): QueryConstraint From e6e4fa00417535d65d842b16bdf75253a221307b Mon Sep 17 00:00:00 2001 From: nbransby Date: Tue, 27 Feb 2024 11:15:58 +0800 Subject: [PATCH 24/27] add PersistentTabManager interface --- .../dev/gitlive/firebase/firestore/externals/firestore.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt index 1837975c2..d4f02686e 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt @@ -292,5 +292,11 @@ external interface FirestoreLocalCache { val kind: String } +external interface PersistentTabManager { + val kind: String +} + external fun memoryLocalCache(): FirestoreLocalCache external fun persistentLocalCache(settings: dynamic = definedExternally): FirestoreLocalCache +external fun persistentSingleTabManager(settings: dynamic = definedExternally): PersistentTabManager +external fun persistentMultipleTabManager(): PersistentTabManager From f75b2408b45e6f8621dd5e0edf8a26759babee2d Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 1 Mar 2024 15:36:31 +0100 Subject: [PATCH 25/27] Use Wrappers rather than Abstract classes --- .../dev/gitlive/firebase/database/database.kt | 41 +++++++---- .../dev/gitlive/firebase/database/database.kt | 73 ++++++++++--------- .../dev/gitlive/firebase/database/database.kt | 39 ++++++---- .../dev/gitlive/firebase/database/database.kt | 37 ++++++---- .../gitlive/firebase/functions/functions.kt | 14 ++-- .../gitlive/firebase/functions/functions.kt | 21 +++--- .../gitlive/firebase/functions/functions.kt | 14 ++-- .../gitlive/firebase/functions/functions.kt | 19 ++--- 8 files changed, 148 insertions(+), 110 deletions(-) diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index 55f440699..ffdcc32a9 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 @@ -74,10 +74,10 @@ actual class FirebaseDatabase private constructor(val android: com.google.fireba private var persistenceEnabled = true actual fun reference(path: String) = - DatabaseReference(android.getReference(path), persistenceEnabled) + DatabaseReference(NativeDatabaseReference(android.getReference(path), persistenceEnabled)) actual fun reference() = - DatabaseReference(android.reference, persistenceEnabled) + DatabaseReference(NativeDatabaseReference(android.reference, persistenceEnabled)) actual fun setPersistenceEnabled(enabled: Boolean) = android.setPersistenceEnabled(enabled).also { persistenceEnabled = enabled } @@ -89,8 +89,8 @@ actual class FirebaseDatabase private constructor(val android: com.google.fireba android.useEmulator(host, port) } -actual data class NativeQuery( - val android: com.google.firebase.database.Query, +internal actual open class NativeQuery( + open val android: com.google.firebase.database.Query, val persistenceEnabled: Boolean, ) @@ -183,25 +183,26 @@ actual open class Query internal actual constructor( override fun toString() = android.toString() } -actual class DatabaseReference internal constructor( +@PublishedApi +internal actual class NativeDatabaseReference internal constructor( override val android: com.google.firebase.database.DatabaseReference, persistenceEnabled: Boolean -): BaseDatabaseReference(NativeQuery(android, persistenceEnabled)) { +): NativeQuery(android, persistenceEnabled) { actual val key get() = android.key val database = FirebaseDatabase(android.database) - actual fun child(path: String) = DatabaseReference(android.child(path), persistenceEnabled) + actual fun child(path: String) = NativeDatabaseReference(android.child(path), persistenceEnabled) - actual fun push() = DatabaseReference(android.push(), persistenceEnabled) - actual fun onDisconnect() = OnDisconnect(android.onDisconnect(), persistenceEnabled, database) + actual fun push() = NativeDatabaseReference(android.push(), persistenceEnabled) + actual fun onDisconnect() = NativeOnDisconnect(android.onDisconnect(), persistenceEnabled, database) - override suspend fun setValueEncoded(encodedValue: Any?) = android.setValue(encodedValue) + actual suspend fun setValueEncoded(encodedValue: Any?) = android.setValue(encodedValue) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @Suppress("UNCHECKED_CAST") - override suspend fun updateEncodedChildren(encodedUpdate: Any?) = + actual suspend fun updateEncodedChildren(encodedUpdate: Any?) = android.updateChildren(encodedUpdate as Map) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } @@ -245,6 +246,9 @@ actual class DatabaseReference internal constructor( return deferred.await() } } + +val DatabaseReference.android get() = nativeReference.android + @Suppress("UNCHECKED_CAST") actual class DataSnapshot internal constructor( val android: com.google.firebase.database.DataSnapshot, @@ -255,7 +259,7 @@ actual class DataSnapshot internal constructor( actual val key get() = android.key - actual val ref: DatabaseReference get() = DatabaseReference(android.ref, persistenceEnabled) + actual val ref: DatabaseReference get() = DatabaseReference(NativeDatabaseReference(android.ref, persistenceEnabled)) actual val value get() = android.value @@ -270,11 +274,12 @@ actual class DataSnapshot internal constructor( actual val children: Iterable get() = android.children.map { DataSnapshot(it, persistenceEnabled) } } -actual class OnDisconnect internal constructor( +@PublishedApi +internal actual class NativeOnDisconnect internal constructor( val android: com.google.firebase.database.OnDisconnect, val persistenceEnabled: Boolean, val database: FirebaseDatabase, -) : BaseOnDisconnect() { +) { actual suspend fun removeValue() = android.removeValue() .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } @@ -284,14 +289,18 @@ actual class OnDisconnect internal constructor( .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - override suspend fun setValue(encodedValue: Any?) = android.setValue(encodedValue) + actual suspend fun setValue(encodedValue: Any?) = android.setValue(encodedValue) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } - override suspend fun updateEncodedChildren(encodedUpdate: Map) = + actual suspend fun updateEncodedChildren(encodedUpdate: Map) = android.updateChildren(encodedUpdate) .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } .run { Unit } } +val OnDisconnect.android get() = native.android +val OnDisconnect.persistenceEnabled get() = native.persistenceEnabled +val OnDisconnect.database get() = native.database + actual typealias DatabaseException = com.google.firebase.database.DatabaseException 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 5f319de76..d1e272a38 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 @@ -52,7 +52,7 @@ data class ChildEvent internal constructor( } } -expect class NativeQuery +internal expect open class NativeQuery expect open class Query internal constructor(nativeQuery: NativeQuery) { val valueEvents: Flow @@ -73,7 +73,26 @@ expect open class Query internal constructor(nativeQuery: NativeQuery) { fun equalTo(value: Boolean, key: String? = null): Query } -abstract class BaseDatabaseReference internal constructor(nativeQuery: NativeQuery) : Query(nativeQuery) { +@PublishedApi +internal expect class NativeDatabaseReference : NativeQuery { + val key: String? + fun push(): NativeDatabaseReference + suspend fun setValueEncoded(encodedValue: Any?) + suspend fun updateEncodedChildren(encodedUpdate: Any?) + fun child(path: String): NativeDatabaseReference + fun onDisconnect(): NativeOnDisconnect + + suspend fun removeValue() + + suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot +} + +class DatabaseReference internal constructor(@PublishedApi internal val nativeReference: NativeDatabaseReference) : Query(nativeReference) { + + val key: String? = nativeReference.key + fun push(): DatabaseReference = DatabaseReference(nativeReference.push()) + fun child(path: String): DatabaseReference = DatabaseReference(nativeReference.child(path)) + fun onDisconnect(): OnDisconnect = OnDisconnect(nativeReference.onDisconnect()) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { this.encodeDefaults = encodeDefaults }")) suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = @@ -81,38 +100,25 @@ abstract class BaseDatabaseReference internal constructor(nativeQuery: NativeQue this.encodeDefaults = encodeDefaults } suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - setValueEncoded(encode(value, buildSettings)) + nativeReference.setValueEncoded(encode(value, buildSettings)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { this.encodeDefaults = encodeDefaults }")) suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = setValue(strategy, value) { this.encodeDefaults = encodeDefaults } - suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValueEncoded(encode(strategy, value, buildSettings)) - - @PublishedApi - internal abstract suspend fun setValueEncoded(encodedValue: Any?) + suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = nativeReference.setValueEncoded(encode(strategy, value, 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 } - suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedChildren( + suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = nativeReference.updateEncodedChildren( encode(update, buildSettings)) - @PublishedApi - internal abstract suspend fun updateEncodedChildren(encodedUpdate: Any?) -} - -expect class DatabaseReference : BaseDatabaseReference { - val key: String? - fun push(): DatabaseReference - fun child(path: String): DatabaseReference - fun onDisconnect(): OnDisconnect + suspend fun removeValue() = nativeReference.removeValue() - suspend fun removeValue() - - suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot + suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot = nativeReference.runTransaction(strategy, buildSettings, transactionUpdate) } expect class DataSnapshot { @@ -129,31 +135,30 @@ expect class DataSnapshot { expect class DatabaseException(message: String?, cause: Throwable?) : RuntimeException -abstract class BaseOnDisconnect internal constructor() { +@PublishedApi +internal expect class NativeOnDisconnect { + suspend fun removeValue() + suspend fun cancel() + suspend fun setValue(encodedValue: Any?) + suspend fun updateEncodedChildren(encodedUpdate: Map) +} + +class OnDisconnect internal constructor(@PublishedApi internal val native: NativeOnDisconnect) { + suspend fun removeValue() = native.removeValue() + suspend fun cancel() = native.cancel() @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { this.encodeDefaults = encodeDefaults }")) suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = setValue(value) { this.encodeDefaults = encodeDefaults } suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = - setValue(encode(value, buildSettings)) + native.setValue(encode(value, buildSettings)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { this.encodeDefaults = encodeDefaults }")) suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = setValue(strategy, value) { this.encodeDefaults = encodeDefaults } suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setValue(encode(strategy, value, buildSettings)) - @PublishedApi - internal abstract suspend fun setValue(encodedValue: Any?) - - suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedChildren(update.mapValues { (_, it) -> encode(it, buildSettings) }) + suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncodedChildren(update.mapValues { (_, it) -> encode(it, 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 } - - @PublishedApi - internal abstract suspend fun updateEncodedChildren(encodedUpdate: Map) -} - -expect class OnDisconnect : BaseOnDisconnect { - suspend fun removeValue() - suspend fun cancel() } 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 b6451234e..a78a26fe1 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 @@ -55,10 +55,10 @@ actual fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase = actual class FirebaseDatabase internal constructor(val ios: FIRDatabase) { actual fun reference(path: String) = - DatabaseReference(ios.referenceWithPath(path), ios.persistenceEnabled) + DatabaseReference(NativeDatabaseReference(ios.referenceWithPath(path), ios.persistenceEnabled)) actual fun reference() = - DatabaseReference(ios.reference(), ios.persistenceEnabled) + DatabaseReference(NativeDatabaseReference(ios.reference(), ios.persistenceEnabled)) actual fun setPersistenceEnabled(enabled: Boolean) { ios.persistenceEnabled = enabled @@ -78,7 +78,7 @@ fun Type.toEventType() = when(this) { REMOVED -> FIRDataEventTypeChildRemoved } -actual data class NativeQuery( +internal actual open class NativeQuery( open val ios: FIRDatabaseQuery, val persistenceEnabled: Boolean ) @@ -147,25 +147,26 @@ actual open class Query internal actual constructor( override fun toString() = ios.toString() } -actual class DatabaseReference internal constructor( +@PublishedApi +internal actual class NativeDatabaseReference internal constructor( override val ios: FIRDatabaseReference, persistenceEnabled: Boolean -): BaseDatabaseReference(NativeQuery(ios, persistenceEnabled)) { +): NativeQuery(ios, persistenceEnabled) { actual val key get() = ios.key - actual fun child(path: String) = DatabaseReference(ios.child(path), persistenceEnabled) + actual fun child(path: String) = NativeDatabaseReference(ios.child(path), persistenceEnabled) - actual fun push() = DatabaseReference(ios.childByAutoId(), persistenceEnabled) - actual fun onDisconnect() = OnDisconnect(ios, persistenceEnabled) + actual fun push() = NativeDatabaseReference(ios.childByAutoId(), persistenceEnabled) + actual fun onDisconnect() = NativeOnDisconnect(ios, persistenceEnabled) - override suspend fun setValueEncoded(encodedValue: Any?) { + actual suspend fun setValueEncoded(encodedValue: Any?) { ios.await(persistenceEnabled) { setValue(encodedValue, it) } } @Suppress("UNCHECKED_CAST") - override suspend fun updateEncodedChildren(encodedValue: Any?) { - ios.await(persistenceEnabled) { updateChildValues(encodedValue as Map, it) } + actual suspend fun updateEncodedChildren(encodedUpdate: Any?) { + ios.await(persistenceEnabled) { updateChildValues(encodedUpdate as Map, it) } } actual suspend fun removeValue() { @@ -192,6 +193,8 @@ actual class DatabaseReference internal constructor( } } +val DatabaseReference.ios: FIRDatabaseReference get() = nativeReference.ios + @Suppress("UNCHECKED_CAST") actual class DataSnapshot internal constructor( val ios: FIRDataSnapshot, @@ -202,7 +205,7 @@ actual class DataSnapshot internal constructor( actual val key: String? get() = ios.key - actual val ref: DatabaseReference get() = DatabaseReference(ios.ref, persistenceEnabled) + actual val ref: DatabaseReference get() = DatabaseReference(NativeDatabaseReference(ios.ref, persistenceEnabled)) actual val value get() = ios.value @@ -217,10 +220,11 @@ actual class DataSnapshot internal constructor( actual val children: Iterable get() = ios.children.allObjects.map { DataSnapshot(it as FIRDataSnapshot, persistenceEnabled) } } -actual class OnDisconnect internal constructor( +@PublishedApi +internal actual class NativeOnDisconnect internal constructor( val ios: FIRDatabaseReference, val persistenceEnabled: Boolean -) : BaseOnDisconnect() { +) { actual suspend fun removeValue() { ios.await(persistenceEnabled) { onDisconnectRemoveValueWithCompletionBlock(it) } } @@ -229,16 +233,19 @@ actual class OnDisconnect internal constructor( ios.await(persistenceEnabled) { cancelDisconnectOperationsWithCompletionBlock(it) } } - override suspend fun setValue(encodedValue: Any?) { + actual suspend fun setValue(encodedValue: Any?) { ios.await(persistenceEnabled) { onDisconnectSetValue(encodedValue, it) } } @Suppress("UNCHECKED_CAST") - override suspend fun updateEncodedChildren(encodedUpdate: Map) { + actual suspend fun updateEncodedChildren(encodedUpdate: Map) { ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate as Map, it) } } } +val OnDisconnect.ios: FIRDatabaseReference get() = native.ios +val OnDisconnect.persistenceEnabled get() = native.persistenceEnabled + actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) private suspend inline fun T.awaitResult(whileOnline: Boolean, function: T.(callback: (NSError?, R?) -> Unit) -> Unit): R { 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 218dd2958..cf0023d0e 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 @@ -67,15 +67,15 @@ actual fun Firebase.database(app: FirebaseApp, url: String) = rethrow { FirebaseDatabase(getDatabase(app = app.js, url = url)) } actual class FirebaseDatabase internal constructor(val js: Database) { - actual fun reference(path: String) = rethrow { DatabaseReference(ref(js, path), js) } - actual fun reference() = rethrow { DatabaseReference(ref(js), js) } + actual fun reference(path: String) = rethrow { DatabaseReference(NativeDatabaseReference(ref(js, path), js)) } + actual fun reference() = rethrow { DatabaseReference(NativeDatabaseReference(ref(js), js)) } actual fun setPersistenceEnabled(enabled: Boolean) {} actual fun setLoggingEnabled(enabled: Boolean) = rethrow { enableLogging(enabled) } actual fun useEmulator(host: String, port: Int) = rethrow { connectDatabaseEmulator(js, host, port) } } -actual data class NativeQuery( - val js: JsQuery, +internal actual open class NativeQuery( + open val js: JsQuery, val database: Database ) @@ -158,24 +158,25 @@ actual open class Query internal actual constructor( } -actual class DatabaseReference internal constructor( +@PublishedApi +internal actual class NativeDatabaseReference internal constructor( override val js: JsDatabaseReference, database: Database -) : BaseDatabaseReference(NativeQuery(js, database)) { +) : NativeQuery(js, database) { actual val key get() = rethrow { js.key } - actual fun push() = rethrow { DatabaseReference(push(js), database) } - actual fun child(path: String) = rethrow { DatabaseReference(child(js, path), database) } + actual fun push() = rethrow { NativeDatabaseReference(push(js), database) } + actual fun child(path: String) = rethrow { NativeDatabaseReference(child(js, path), database) } - actual fun onDisconnect() = rethrow { OnDisconnect(onDisconnect(js), database) } + actual fun onDisconnect() = rethrow { NativeOnDisconnect(onDisconnect(js), database) } actual suspend fun removeValue() = rethrow { remove(js).awaitWhileOnline(database) } - override suspend fun setValueEncoded(encodedValue: Any?) = rethrow { + actual suspend fun setValueEncoded(encodedValue: Any?) = rethrow { set(js, encodedValue).awaitWhileOnline(database) } - override suspend fun updateEncodedChildren(encodedUpdate: Any?) = + actual suspend fun updateEncodedChildren(encodedUpdate: Any?) = rethrow { update(js, encodedUpdate ?: json()).awaitWhileOnline(database) } @@ -211,26 +212,30 @@ actual class DataSnapshot internal constructor( } } actual val ref: DatabaseReference - get() = DatabaseReference(js.ref, database) + get() = DatabaseReference(NativeDatabaseReference(js.ref, database)) } -actual class OnDisconnect internal constructor( +@PublishedApi +internal actual class NativeOnDisconnect internal constructor( val js: JsOnDisconnect, val database: Database -) : BaseOnDisconnect() { +) { actual suspend fun removeValue() = rethrow { js.remove().awaitWhileOnline(database) } actual suspend fun cancel() = rethrow { js.cancel().awaitWhileOnline(database) } - override suspend fun setValue(encodedValue: Any?) = + actual suspend fun setValue(encodedValue: Any?) = rethrow { js.set(encodedValue).awaitWhileOnline(database) } - override suspend fun updateEncodedChildren(encodedUpdate: Map) = + actual suspend fun updateEncodedChildren(encodedUpdate: Map) = rethrow { js.update(encodedUpdate).awaitWhileOnline(database) } } +val OnDisconnect.js get() = native.js +val OnDisconnect.database get() = native.database + actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) { constructor(error: dynamic) : this("${error.code ?: "UNKNOWN"}: ${error.message}", error.unsafeCast()) } 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 a2f62b55e..d93f743ec 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 @@ -26,17 +26,21 @@ actual fun Firebase.functions(app: FirebaseApp, region: String) = actual data class FirebaseFunctions internal constructor(val android: com.google.firebase.functions.FirebaseFunctions) { actual fun httpsCallable(name: String, timeout: Long?) = - HttpsCallableReference(android.getHttpsCallable(name).apply { timeout?.let { setTimeout(it, TimeUnit.MILLISECONDS) } }) + HttpsCallableReference(android.getHttpsCallable(name).apply { timeout?.let { setTimeout(it, TimeUnit.MILLISECONDS) } }.native) actual fun useEmulator(host: String, port: Int) = android.useEmulator(host, port) } -actual class HttpsCallableReference internal constructor(val android: com.google.firebase.functions.HttpsCallableReference) : BaseHttpsCallableReference() { - actual suspend operator fun invoke() = HttpsCallableResult(android.call().await()) - - override suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(android.call(encodedData).await()) +@PublishedApi +internal actual data class NativeHttpsCallableReference(val android: com.google.firebase.functions.HttpsCallableReference){ + actual suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(android.call(encodedData).await()) + actual suspend fun invoke(): HttpsCallableResult = HttpsCallableResult(android.call().await()) } +internal val com.google.firebase.functions.HttpsCallableReference.native get() = NativeHttpsCallableReference(this) + +val HttpsCallableReference.android: com.google.firebase.functions.HttpsCallableReference get() = native.android + actual class HttpsCallableResult constructor(val android: com.google.firebase.functions.HttpsCallableResult) { actual inline fun data() = 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 1ec19d65d..9a152a430 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 @@ -13,25 +13,28 @@ expect class FirebaseFunctions { fun useEmulator(host: String, port: Int) } -abstract class BaseHttpsCallableReference { +@PublishedApi +internal expect class NativeHttpsCallableReference { + suspend fun invoke(encodedData: Any): HttpsCallableResult + suspend fun invoke(): HttpsCallableResult +} + +class HttpsCallableReference internal constructor( + @PublishedApi + internal val native: NativeHttpsCallableReference +) { @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(data) { this.encodeDefaults = encodeDefaults }")) suspend inline operator fun invoke(data: T, encodeDefaults: Boolean) = invoke(data) { this.encodeDefaults = encodeDefaults } - suspend inline operator fun invoke(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = invoke(encode(data, buildSettings)!!) + suspend inline operator fun invoke(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = native.invoke(encodedData = encode(data, buildSettings)!!) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(strategy, data) { this.encodeDefaults = encodeDefaults }")) suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): HttpsCallableResult = invoke(strategy, data) { this.encodeDefaults = encodeDefaults } suspend inline operator fun invoke(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = invoke(encode(strategy, data, buildSettings)!!) - - @PublishedApi - internal abstract suspend fun invoke(encodedData: Any): HttpsCallableResult -} - -expect class HttpsCallableReference : BaseHttpsCallableReference { - suspend operator fun invoke(): HttpsCallableResult + suspend operator fun invoke(): HttpsCallableResult = native.invoke() } expect class HttpsCallableResult { 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 1104c8027..907508278 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 @@ -34,17 +34,21 @@ actual fun Firebase.functions( actual data class FirebaseFunctions internal constructor(val ios: FIRFunctions) { actual fun httpsCallable(name: String, timeout: Long?) = - HttpsCallableReference(ios.HTTPSCallableWithName(name).apply { timeout?.let { setTimeoutInterval(it/1000.0) } }) + HttpsCallableReference(ios.HTTPSCallableWithName(name).apply { timeout?.let { setTimeoutInterval(it/1000.0) } }.native) actual fun useEmulator(host: String, port: Int) = ios.useEmulatorWithHost(host, port.toLong()) } -actual class HttpsCallableReference internal constructor(val ios: FIRHTTPSCallable) : BaseHttpsCallableReference() { - actual suspend operator fun invoke() = HttpsCallableResult(ios.awaitResult { callWithCompletion(it) }) - - override suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(ios.awaitResult { callWithObject(encodedData, it) }) +@PublishedApi +internal actual data class NativeHttpsCallableReference(val ios: FIRHTTPSCallable) { + actual suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(ios.awaitResult { callWithObject(encodedData, it) }) + actual suspend fun invoke(): HttpsCallableResult = HttpsCallableResult(ios.awaitResult { callWithCompletion(it) }) } +internal val FIRHTTPSCallable.native get() = NativeHttpsCallableReference(this) + +val HttpsCallableReference.ios: FIRHTTPSCallable get() = native.ios + actual class HttpsCallableResult constructor(val ios: FIRHTTPSCallableResult) { actual inline fun data() = 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 b340d2aa5..b8b9b7bee 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 @@ -26,23 +26,24 @@ actual fun Firebase.functions(app: FirebaseApp, region: String) = actual class FirebaseFunctions internal constructor(val js: Functions) { actual fun httpsCallable(name: String, timeout: Long?) = - rethrow { HttpsCallableReference(httpsCallable(js, name, timeout?.let { json("timeout" to timeout.toDouble()) })) } + rethrow { HttpsCallableReference( httpsCallable(js, name, timeout?.let { json("timeout" to timeout.toDouble()) }).native) } actual fun useEmulator(host: String, port: Int) = connectFunctionsEmulator(js, host, port) } -@Suppress("UNCHECKED_CAST") -actual class HttpsCallableReference internal constructor(val js: HttpsCallable) : BaseHttpsCallableReference() { - - actual suspend operator fun invoke() = - rethrow { HttpsCallableResult(js().await()) } - - override suspend fun invoke(encodedData: Any): HttpsCallableResult = rethrow { +@PublishedApi +internal actual data class NativeHttpsCallableReference(val js: HttpsCallable) { + actual suspend fun invoke(encodedData: Any): HttpsCallableResult = rethrow { HttpsCallableResult(js(encodedData).await()) } + actual suspend fun invoke(): HttpsCallableResult = rethrow { HttpsCallableResult(js().await()) } } -actual class HttpsCallableResult constructor(val js: JsHttpsCallableResult) { +internal val HttpsCallable.native get() = NativeHttpsCallableReference(this) + +val HttpsCallableReference.js: HttpsCallable get() = native.js + +actual class HttpsCallableResult(val js: JsHttpsCallableResult) { actual inline fun data() = rethrow { decode(value = js.data) } From f8eb4fd78919ab8b44937e66fb776d894454f04e Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 1 Mar 2024 17:10:54 +0100 Subject: [PATCH 26/27] Use wrappes + extension methods instead of abstract class --- .../gitlive/firebase/firestore/_encoders.kt | 2 +- .../gitlive/firebase/firestore/firestore.kt | 146 ++++++------ .../firestore/DocumentReferenceSerializer.kt | 4 +- .../gitlive/firebase/firestore/firestore.kt | 216 +++++++++++------- .../gitlive/firebase/firestore/_encoders.kt | 2 +- .../gitlive/firebase/firestore/firestore.kt | 144 ++++++------ .../gitlive/firebase/firestore/_encoders.kt | 2 +- .../gitlive/firebase/firestore/firestore.kt | 119 +++++----- 8 files changed, 362 insertions(+), 273 deletions(-) diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt index d12bda859..84445fb4d 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -5,6 +5,6 @@ internal actual fun isSpecialValue(value: Any) = when(value) { is NativeFieldValue, is NativeGeoPoint, is NativeTimestamp, - is NativeDocumentReference -> true + is NativeDocumentReferenceType -> true else -> false } 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 3a1a57119..242618671 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 @@ -26,19 +26,19 @@ actual fun Firebase.firestore(app: FirebaseApp) = actual class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) { - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) + actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReference(android.collection(collectionPath))) - actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId)) + actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId).native) - actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) + actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(android.document(documentPath))) - actual fun batch() = WriteBatch(android.batch()) + actual fun batch() = WriteBatch(NativeWriteBatch(android.batch())) actual fun setLoggingEnabled(loggingEnabled: Boolean) = com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) actual suspend fun runTransaction(func: suspend Transaction.() -> T): T = - android.runTransaction { runBlocking { Transaction(it).func() } }.await() + android.runTransaction { runBlocking { Transaction(NativeTransaction(it)).func() } }.await() actual suspend fun clearPersistence() = android.clearPersistence().await().run { } @@ -67,39 +67,40 @@ actual class FirebaseFirestore(val android: com.google.firebase.firestore.Fireba } -val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { +internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() is SetOptions.Overwrite -> null is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) } -actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) : BaseWriteBatch() { +@PublishedApi +internal actual class NativeWriteBatch(val android: com.google.firebase.firestore.WriteBatch) { - override fun setEncoded( + actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): BaseWriteBatch = (setOptions.android?.let { + ): NativeWriteBatch = (setOptions.android?.let { android.set(documentRef.android, encodedData, it) } ?: android.set(documentRef.android, encodedData)).let { this } @Suppress("UNCHECKED_CAST") - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = android.update(documentRef.android, encodedData as Map).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any) = android.update(documentRef.android, encodedData as Map).let { this } - override fun updateEncodedFieldsAndValues( + actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseWriteBatch = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> android.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } - override fun updateEncodedFieldPathsAndValues( + actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseWriteBatch = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> android.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } @@ -111,13 +112,16 @@ actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) : } } -actual class Transaction(val android: com.google.firebase.firestore.Transaction) : BaseTransaction() { +val WriteBatch.android get() = native.android - override fun setEncoded( +@PublishedApi +internal actual class NativeTransaction(val android: com.google.firebase.firestore.Transaction) { + + actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): BaseTransaction { + ): NativeTransaction { setOptions.android?.let { android.set(documentRef.android, encodedData, it) } ?: android.set(documentRef.android, encodedData) @@ -125,16 +129,16 @@ actual class Transaction(val android: com.google.firebase.firestore.Transaction) } @Suppress("UNCHECKED_CAST") - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = android.update(documentRef.android, encodedData as Map).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any) = android.update(documentRef.android, encodedData as Map).let { this } - override fun updateEncodedFieldsAndValues( + actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseTransaction = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> android.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } - override fun updateEncodedFieldPathsAndValues( + actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> @@ -145,30 +149,32 @@ actual class Transaction(val android: com.google.firebase.firestore.Transaction) android.delete(documentRef.android).let { this } actual suspend fun get(documentRef: DocumentReference) = - DocumentSnapshot(android.get(documentRef.android)) + NativeDocumentSnapshot(android.get(documentRef.android)) } +val Transaction.android get() = native.android + /** A class representing a platform specific Firebase DocumentReference. */ -actual typealias NativeDocumentReference = com.google.firebase.firestore.DocumentReference +actual typealias NativeDocumentReferenceType = com.google.firebase.firestore.DocumentReference -@Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { - val android: NativeDocumentReference by ::nativeValue +@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: CollectionReference - get() = CollectionReference(android.parent) + actual val parent: NativeCollectionReference + get() = NativeCollectionReference(android.parent) - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) + actual fun collection(collectionPath: String) = NativeCollectionReference(android.collection(collectionPath)) actual suspend fun get() = - DocumentSnapshot(android.get().await()) + NativeDocumentSnapshot(android.get().await()) - override suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) { + actual suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) { val task = (setOptions.android?.let { android.set(encodedData, it) } ?: android.set(encodedData)) @@ -176,53 +182,57 @@ actual class DocumentReference actual constructor(internal actual val nativeValu } @Suppress("UNCHECKED_CAST") - override suspend fun updateEncoded(encodedData: Any) { + actual suspend fun updateEncoded(encodedData: Any) { android.update(encodedData as Map).await() } - override suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { android.update(encodedFieldsAndValues.toMap()) }?.await() } - override suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } ?.performUpdate { field, value, moreFieldsAndValues -> android.update(field, value, *moreFieldsAndValues) }?.await() } - override suspend fun delete() { + actual suspend fun delete() { android.delete().await() } - actual val snapshots: Flow get() = snapshots() + actual val snapshots: Flow get() = snapshots() actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(DocumentSnapshot(snapshot)) } + snapshot?.let { trySend(NativeDocumentSnapshot(snapshot)) } exception?.let { close(exception) } } awaitClose { listener.remove() } } override fun equals(other: Any?): Boolean = - this === other || other is DocumentReference && nativeValue == other.nativeValue + this === other || other is NativeDocumentReference && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() } -actual typealias NativeQuery = AndroidQuery +val DocumentReference.android get() = native.android + +@PublishedApi +internal actual open class NativeQuery(open val android: AndroidQuery) +internal val AndroidQuery.native get() = NativeQuery(this) actual open class Query internal actual constructor(nativeQuery: NativeQuery) { - open val android = nativeQuery + open val android = nativeQuery.android actual suspend fun get() = QuerySnapshot(android.get().await()) - actual fun limit(limit: Number) = Query(android.limit(limit.toLong())) + actual fun limit(limit: Number) = Query(NativeQuery(android.limit(limit.toLong()))) actual val snapshots get() = callbackFlow { val listener = android.addSnapshotListener { snapshot, exception -> @@ -242,7 +252,7 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } internal actual fun where(filter: Filter) = Query( - android.where(filter.toAndroidFilter()) + android.where(filter.toAndroidFilter()).native ) private fun Filter.toAndroidFilter(): AndroidFilter = when (this) { @@ -308,39 +318,42 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } } - internal actual fun _orderBy(field: String, direction: Direction) = Query(android.orderBy(field, direction)) - internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(android.orderBy(field.android, direction)) + 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)) - internal actual fun _startAfter(vararg fieldValues: Any) = Query(android.startAfter(*fieldValues)) - internal actual fun _startAt(document: DocumentSnapshot) = Query(android.startAt(document.android)) - internal actual fun _startAt(vararg fieldValues: Any) = Query(android.startAt(*fieldValues)) + 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)) - internal actual fun _endBefore(vararg fieldValues: Any) = Query(android.endBefore(*fieldValues)) - internal actual fun _endAt(document: DocumentSnapshot) = Query(android.endAt(document.android)) - internal actual fun _endAt(vararg fieldValues: Any) = Query(android.endAt(*fieldValues)) + internal actual fun _endBefore(document: DocumentSnapshot) = Query(android.endBefore(document.android).native) + internal actual fun _endBefore(vararg fieldValues: Any) = Query(android.endBefore(*fieldValues).native) + internal actual fun _endAt(document: DocumentSnapshot) = Query(android.endAt(document.android).native) + internal actual fun _endAt(vararg fieldValues: Any) = Query(android.endAt(*fieldValues).native) } actual typealias Direction = com.google.firebase.firestore.Query.Direction actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type -actual class CollectionReference(override val android: com.google.firebase.firestore.CollectionReference) : BaseCollectionReference(android) { +@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: DocumentReference - get() = DocumentReference(android.document()) + actual val document: NativeDocumentReference + get() = NativeDocumentReference(android.document()) - actual val parent: DocumentReference? - get() = android.parent?.let{DocumentReference(it)} + actual val parent: NativeDocumentReference? + get() = android.parent?.let{ NativeDocumentReference(it) } - actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) + actual fun document(documentPath: String) = NativeDocumentReference(android.document(documentPath)) - override suspend fun addEncoded(data: Any) = DocumentReference(android.add(data).await()) + actual suspend fun addEncoded(data: Any) = NativeDocumentReference(android.add(data).await()) } +val CollectionReference.android get() = native.android + actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code @@ -349,7 +362,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(it) } + get() = android.documents.map { DocumentSnapshot(NativeDocumentSnapshot(it)) } actual val documentChanges get() = android.documentChanges.map { DocumentChange(it) } actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.metadata) @@ -357,7 +370,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(android.document) + get() = DocumentSnapshot(NativeDocumentSnapshot(android.document)) actual val newIndex: Int get() = android.newIndex actual val oldIndex: Int @@ -366,13 +379,14 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC get() = android.type } -actual class DocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) : BaseDocumentSnapshot() { +@PublishedApi +internal actual class NativeDocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) { actual val id get() = android.id - actual val reference get() = DocumentReference(android.reference) + actual val reference get() = NativeDocumentReference(android.reference) - override fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = android.get(field, serverTimestampBehavior.toAndroid()) - override fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = android.getData(serverTimestampBehavior.toAndroid()) + 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) @@ -387,6 +401,8 @@ actual class DocumentSnapshot(val android: com.google.firebase.firestore.Documen } } +val DocumentSnapshot.android get() = native.android + actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { actual val hasPendingWrites: Boolean get() = android.hasPendingWrites() actual val isFromCache: Boolean get() = android.isFromCache 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 038edc434..0f2f8fe30 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 @@ -10,10 +10,10 @@ import kotlinx.serialization.SerializationException */ object DocumentReferenceSerializer : KSerializer by SpecialValueSerializer( serialName = "DocumentReference", - toNativeValue = DocumentReference::nativeValue, + toNativeValue = { it.native.nativeValue }, fromNativeValue = { value -> when (value) { - is NativeDocumentReference -> DocumentReference(value) + is NativeDocumentReferenceType -> DocumentReference(NativeDocumentReference(value)) else -> throw SerializationException("Cannot deserialize $value") } } 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 143920a7e..f92436805 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -6,6 +6,7 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.* import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationStrategy @@ -31,7 +32,8 @@ expect class FirebaseFirestore { suspend fun enableNetwork() } -sealed class SetOptions { +@PublishedApi +internal sealed class SetOptions { data object Merge : SetOptions() data object Overwrite : SetOptions() data class MergeFields(val fields: List) : SetOptions() @@ -40,46 +42,57 @@ sealed class SetOptions { } } -abstract class BaseTransaction { +@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 native: NativeTransaction) { @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): BaseTransaction = set(documentRef, data, merge) { + 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 = {}): BaseTransaction = 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, encode(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 = {}): BaseTransaction = 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, encode(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 = {}): BaseTransaction = 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, encode(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) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = 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, encode(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) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = 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, encode(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) { this.encodeDefaults = encodeDefaults } - inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): BaseTransaction = 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, encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseTransaction + @PublishedApi + internal fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): Transaction = Transaction(native.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) { @@ -99,21 +112,20 @@ abstract class BaseTransaction { inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @PublishedApi - internal abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction + internal fun updateEncoded(documentRef: DocumentReference, encodedData: Any): Transaction = Transaction(native.updateEncoded(documentRef, encodedData)) @PublishedApi - internal abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction + internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): Transaction = Transaction(native.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) @PublishedApi - internal abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction -} + internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): Transaction = Transaction(native.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) -expect class Transaction : BaseTransaction { - fun delete(documentRef: DocumentReference): Transaction - suspend fun get(documentRef: DocumentReference): DocumentSnapshot + fun delete(documentRef: DocumentReference): Transaction = Transaction(native.delete(documentRef)) + suspend fun get(documentRef: DocumentReference): DocumentSnapshot = DocumentSnapshot(native.get(documentRef)) } -expect class NativeQuery +@PublishedApi +internal expect open class NativeQuery expect open class Query internal constructor(nativeQuery: NativeQuery) { fun limit(limit: Number): Query @@ -207,13 +219,23 @@ fun Query.endAt(vararg fieldValues: Any) = _endAt(*(fieldValues.mapNotNull { it. internal val Any.safeValue: Any get() = when (this) { is Timestamp -> nativeValue is GeoPoint -> nativeValue - is DocumentReference -> 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 } -abstract class BaseWriteBatch { +@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 native: NativeWriteBatch) { @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) { @@ -258,7 +280,7 @@ abstract class BaseWriteBatch { setEncoded(documentRef, encode(strategy, data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @PublishedApi - internal abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseWriteBatch + internal fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions) = WriteBatch(native.setEncoded(documentRef, encodedData, setOptions)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { this.encodeDefaults = encodeDefaults }")) inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = update(documentRef, data) { @@ -280,114 +302,131 @@ abstract class BaseWriteBatch { inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @PublishedApi - internal abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch + internal fun updateEncoded(documentRef: DocumentReference, encodedData: Any) = WriteBatch(native.updateEncoded(documentRef, encodedData)) @PublishedApi - internal abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch + internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(native.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) @PublishedApi - internal abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch + internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(native.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) + + fun delete(documentRef: DocumentReference): WriteBatch = WriteBatch(native.delete(documentRef)) + suspend fun commit() = native.commit() } -expect class WriteBatch : BaseWriteBatch { +/** A class representing a platform specific Firebase DocumentReference. */ +expect class NativeDocumentReferenceType - fun delete(documentRef: DocumentReference): WriteBatch - suspend fun commit() +@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(): 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 platform specific Firebase DocumentReference. */ -expect class NativeDocumentReference +/** A class representing a Firebase DocumentReference. */ +@Serializable(with = DocumentReferenceSerializer::class) +data class DocumentReference internal constructor(@PublishedApi internal val native: NativeDocumentReference) { -abstract class BaseDocumentReference { + internal val nativeValue get() = native.nativeValue + + val id: String get() = native.id + val path: String get() = native.path + val snapshots: Flow get() = native.snapshots.map(::DocumentSnapshot) + val parent: CollectionReference get() = CollectionReference(native.parent) + fun snapshots(includeMetadataChanges: Boolean = false): Flow = native.snapshots(includeMetadataChanges).map(::DocumentSnapshot) + + fun collection(collectionPath: String): CollectionReference = CollectionReference(native.collection(collectionPath)) + suspend fun get(): DocumentSnapshot = DocumentSnapshot(native.get()) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, merge) { this.encodeDefaults = encodeDefaults }")) suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(data, merge) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = 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(encode(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) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFields(mergeFields.asList())) + suspend inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded(encode(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) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded(encode(data, buildSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + suspend inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.setEncoded(encode(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) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( + 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) @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) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( + 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())) @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) { this.encodeDefaults = encodeDefaults } - suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = setEncoded( + 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())) - @PublishedApi - internal abstract suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) - @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(data) { this.encodeDefaults = encodeDefaults }")) suspend inline fun update(data: T, encodeDefaults: Boolean) = update(data) { this.encodeDefaults = encodeDefaults } - suspend inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(encode(data, buildSettings)!!) + suspend inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncoded(encode(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) { this.encodeDefaults = encodeDefaults } - suspend inline fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncoded(encode(strategy, data, buildSettings)!!) + suspend inline fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncoded(encode(strategy, data, buildSettings)!!) @JvmName("updateFields") - suspend inline fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) + suspend inline fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @JvmName("updateFieldPaths") - suspend inline fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) - - @PublishedApi - internal abstract suspend fun updateEncoded(encodedData: Any) - - @PublishedApi - internal abstract suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) - - @PublishedApi - internal abstract suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) + suspend inline fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = native.updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) - abstract suspend fun delete() + suspend fun delete() = native.delete() } -/** A class representing a Firebase DocumentReference. */ -@Serializable(with = DocumentReferenceSerializer::class) -expect class DocumentReference internal constructor(nativeValue: NativeDocumentReference) : BaseDocumentReference { - internal val nativeValue: NativeDocumentReference - - val id: String +@PublishedApi +internal expect class NativeCollectionReference : NativeQuery { val path: String - val snapshots: Flow - val parent: CollectionReference - fun snapshots(includeMetadataChanges: Boolean = false): Flow + val document: NativeDocumentReference + val parent: NativeDocumentReference? - fun collection(collectionPath: String): CollectionReference - suspend fun get(): DocumentSnapshot + fun document(documentPath: String): NativeDocumentReference + suspend fun addEncoded(data: Any): NativeDocumentReference } -abstract class BaseCollectionReference(nativeQuery: NativeQuery) : Query(nativeQuery) { +data class CollectionReference internal constructor(@PublishedApi internal val native: NativeCollectionReference) : Query(native) { + + val path: String get() = native.path + val document: DocumentReference get() = DocumentReference(native.document) + val parent: DocumentReference? get() = native.parent?.let(::DocumentReference) + + fun document(documentPath: String): DocumentReference = DocumentReference(native.document(documentPath)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(data) { this.encodeDefaults = encodeDefaults }")) suspend inline fun add(data: T, encodeDefaults: Boolean) = add(data) { @@ -406,15 +445,7 @@ abstract class BaseCollectionReference(nativeQuery: NativeQuery) : Query(nativeQ ) @PublishedApi - internal abstract suspend fun addEncoded(data: Any): DocumentReference -} - -expect class CollectionReference : BaseCollectionReference { - val path: String - val document: DocumentReference - val parent: DocumentReference? - - fun document(documentPath: String): DocumentReference + internal suspend fun addEncoded(data: Any): DocumentReference = DocumentReference(native.addEncoded(data)) } expect class FirebaseFirestoreException : FirebaseException @@ -466,28 +497,39 @@ expect class DocumentChange { val type: ChangeType } -abstract class BaseDocumentSnapshot { +@PublishedApi +internal expect class NativeDocumentSnapshot { + + val exists: Boolean + val id: String + val reference: NativeDocumentReference + val metadata: SnapshotMetadata + + fun contains(field: String): Boolean + + fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? +} + +data class DocumentSnapshot internal constructor(@PublishedApi internal val native: NativeDocumentSnapshot) { + + 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 + + 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 abstract fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + internal fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = native.getEncoded(field, serverTimestampBehavior) 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 abstract fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? -} - -expect class DocumentSnapshot : BaseDocumentSnapshot { - - fun contains(field: String): Boolean - - val exists: Boolean - val id: String - val reference: DocumentReference - val metadata: SnapshotMetadata + internal fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = native.encodedData(serverTimestampBehavior) } enum class ServerTimestampBehavior { diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt index ab06e2eed..5bde38bac 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -7,6 +7,6 @@ internal actual fun isSpecialValue(value: Any) = when(value) { is FIRFieldValue, is NativeGeoPoint, is NativeTimestamp, - is NativeDocumentReference -> true + is NativeDocumentReferenceType -> true else -> false } 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 14d04f538..06c46f1e6 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 @@ -29,19 +29,19 @@ actual fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore = FirebaseFir @Suppress("UNCHECKED_CAST") actual class FirebaseFirestore(val ios: FIRFirestore) { - actual fun collection(collectionPath: String) = CollectionReference(ios.collectionWithPath(collectionPath)) + actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReference(ios.collectionWithPath(collectionPath))) - actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId)) + actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId).native) - actual fun document(documentPath: String) = DocumentReference(ios.documentWithPath(documentPath)) + actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(ios.documentWithPath(documentPath))) - actual fun batch() = WriteBatch(ios.batch()) + actual fun batch() = WriteBatch(NativeWriteBatch(ios.batch())) actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = FIRFirestore.enableLogging(loggingEnabled) actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - awaitResult { ios.runTransactionWithBlock({ transaction, _ -> runBlocking { Transaction(transaction!!).func() } }, it) } as T + awaitResult { ios.runTransactionWithBlock({ transaction, _ -> runBlocking { Transaction(NativeTransaction(transaction!!)).func() } }, it) } as T actual suspend fun clearPersistence() = await { ios.clearPersistenceWithCompletion(it) } @@ -73,33 +73,34 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { } @Suppress("UNCHECKED_CAST") -actual class WriteBatch(val ios: FIRWriteBatch) : BaseWriteBatch() { +@PublishedApi +internal actual class NativeWriteBatch(val ios: FIRWriteBatch) { - override fun setEncoded( + actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): BaseWriteBatch = when (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 } - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = ios.updateData(encodedData as Map, documentRef.ios).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatch = ios.updateData(encodedData as Map, documentRef.ios).let { this } - override fun updateEncodedFieldsAndValues( + actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseWriteBatch = ios.updateData( + ): NativeWriteBatch = ios.updateData( encodedFieldsAndValues.toMap(), documentRef.ios ).let { this } - override fun updateEncodedFieldPathsAndValues( + actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseWriteBatch = ios.updateData( + ): NativeWriteBatch = ios.updateData( encodedFieldsAndValues.toMap(), documentRef.ios ).let { this } @@ -110,34 +111,37 @@ actual class WriteBatch(val ios: FIRWriteBatch) : BaseWriteBatch() { actual suspend fun commit() = await { ios.commitWithCompletion(it) } } +val WriteBatch.ios get() = native.ios + @Suppress("UNCHECKED_CAST") -actual class Transaction(val ios: FIRTransaction) : BaseTransaction() { +@PublishedApi +internal actual class NativeTransaction(val ios: FIRTransaction) { - override fun setEncoded( + actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): BaseTransaction = when (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 } - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = ios.updateData(encodedData as Map, documentRef.ios).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransaction = ios.updateData(encodedData as Map, documentRef.ios).let { this } - override fun updateEncodedFieldsAndValues( + actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseTransaction = ios.updateData( + ): NativeTransaction = ios.updateData( encodedFieldsAndValues.toMap(), documentRef.ios ).let { this } - override fun updateEncodedFieldPathsAndValues( + actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseTransaction = ios.updateData( + ): NativeTransaction = ios.updateData( encodedFieldsAndValues.toMap(), documentRef.ios ).let { this } @@ -146,26 +150,28 @@ actual class Transaction(val ios: FIRTransaction) : BaseTransaction() { ios.deleteDocument(documentRef.ios).let { this } actual suspend fun get(documentRef: DocumentReference) = - throwError { DocumentSnapshot(ios.getDocument(documentRef.ios, it)!!) } + throwError { NativeDocumentSnapshot(ios.getDocument(documentRef.ios, it)!!) } } +val Transaction.ios get() = native.ios + /** A class representing a platform specific Firebase DocumentReference. */ -actual typealias NativeDocumentReference = FIRDocumentReference +actual typealias NativeDocumentReferenceType = FIRDocumentReference @Suppress("UNCHECKED_CAST") -@Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { +@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(DocumentSnapshot(snapshot)) } + snapshot?.let { trySend(NativeDocumentSnapshot(snapshot)) } error?.let { close(error.toException()) } } awaitClose { listener.remove() } } - val ios: NativeDocumentReference by ::nativeValue + val ios: NativeDocumentReferenceType by ::nativeValue actual val id: String get() = ios.documentID @@ -173,16 +179,16 @@ actual class DocumentReference actual constructor(internal actual val nativeValu actual val path: String get() = ios.path - actual val parent: CollectionReference - get() = CollectionReference(ios.parent) + actual val parent: NativeCollectionReference + get() = NativeCollectionReference(ios.parent) - actual fun collection(collectionPath: String) = CollectionReference(ios.collectionWithPath(collectionPath)) + actual fun collection(collectionPath: String) = NativeCollectionReference(ios.collectionWithPath(collectionPath)) actual suspend fun get() = - DocumentSnapshot(awaitResult { ios.getDocumentWithCompletion(it) }) + NativeDocumentSnapshot(awaitResult { ios.getDocumentWithCompletion(it) }) - override suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) = await { + 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) @@ -191,43 +197,47 @@ actual class DocumentReference actual constructor(internal actual val nativeValu } } - override suspend fun updateEncoded(encodedData: Any) = await { + actual suspend fun updateEncoded(encodedData: Any) = await { ios.updateData(encodedData as Map, it) } - override suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = await { + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = await { ios.updateData(encodedFieldsAndValues.toMap(), it) } - override suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) = await { + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) = await { ios.updateData(encodedFieldsAndValues.toMap(), it) } - override suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } + actual suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } - actual val snapshots get() = callbackFlow { + actual val snapshots get() = callbackFlow { val listener = ios.addSnapshotListener { snapshot, error -> - snapshot?.let { trySend(DocumentSnapshot(snapshot)) } + snapshot?.let { trySend(NativeDocumentSnapshot(snapshot)) } error?.let { close(error.toException()) } } awaitClose { listener.remove() } } override fun equals(other: Any?): Boolean = - this === other || other is DocumentReference && nativeValue == other.nativeValue + this === other || other is NativeDocumentReference && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() } -actual typealias NativeQuery = FIRQuery +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 + open val ios: FIRQuery = nativeQuery.ios actual suspend fun get() = QuerySnapshot(awaitResult { ios.getDocumentsWithCompletion(it) }) - actual fun limit(limit: Number) = Query(ios.queryLimitedTo(limit.toLong())) + actual fun limit(limit: Number) = Query(ios.queryLimitedTo(limit.toLong()).native) actual val snapshots get() = callbackFlow { val listener = ios.addSnapshotListener { snapshot, error -> @@ -246,7 +256,7 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } internal actual fun where(filter: Filter): Query = Query( - ios.queryWhereFilter(filter.toFIRFilter()) + ios.queryWhereFilter(filter.toFIRFilter()).native ) private fun Filter.toFIRFilter(): FIRFilter = when (this) { @@ -278,36 +288,39 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } } - internal actual fun _orderBy(field: String, direction: Direction) = Query(ios.queryOrderedByField(field, direction == Direction.DESCENDING)) - internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(ios.queryOrderedByFieldPath(field.ios, direction == Direction.DESCENDING)) + 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)) - internal actual fun _startAfter(vararg fieldValues: Any) = Query(ios.queryStartingAfterValues(fieldValues.asList())) - internal actual fun _startAt(document: DocumentSnapshot) = Query(ios.queryStartingAtDocument(document.ios)) - internal actual fun _startAt(vararg fieldValues: Any) = Query(ios.queryStartingAtValues(fieldValues.asList())) + 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)) - internal actual fun _endBefore(vararg fieldValues: Any) = Query(ios.queryEndingBeforeValues(fieldValues.asList())) - internal actual fun _endAt(document: DocumentSnapshot) = Query(ios.queryEndingAtDocument(document.ios)) - internal actual fun _endAt(vararg fieldValues: Any) = Query(ios.queryEndingAtValues(fieldValues.asList())) + 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) } @Suppress("UNCHECKED_CAST") -actual class CollectionReference(override val ios: FIRCollectionReference) : BaseCollectionReference(ios) { +@PublishedApi +internal actual class NativeCollectionReference(override val ios: FIRCollectionReference) : NativeQuery(ios) { actual val path: String get() = ios.path - actual val document get() = DocumentReference(ios.documentWithAutoID()) + actual val document get() = NativeDocumentReference(ios.documentWithAutoID()) - actual val parent get() = ios.parent?.let{DocumentReference(it)} + actual val parent get() = ios.parent?.let{ NativeDocumentReference(it) } - actual fun document(documentPath: String) = DocumentReference(ios.documentWithPath(documentPath)) + actual fun document(documentPath: String) = NativeDocumentReference(ios.documentWithPath(documentPath)) - override suspend fun addEncoded(data: Any) = DocumentReference(await { ios.addDocumentWithData(data as Map, it) }) + actual suspend fun addEncoded(data: Any) = NativeDocumentReference(await { ios.addDocumentWithData(data as Map, it) }) } +val CollectionReference.ios get() = native.ios + actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code @@ -369,7 +382,7 @@ fun NSError.toException() = when(domain) { actual class QuerySnapshot(val ios: FIRQuerySnapshot) { actual val documents - get() = ios.documents.map { DocumentSnapshot(it as FIRDocumentSnapshot) } + get() = ios.documents.map { DocumentSnapshot(NativeDocumentSnapshot(it as FIRDocumentSnapshot)) } actual val documentChanges get() = ios.documentChanges.map { DocumentChange(it as FIRDocumentChange) } actual val metadata: SnapshotMetadata get() = SnapshotMetadata(ios.metadata) @@ -377,7 +390,7 @@ actual class QuerySnapshot(val ios: FIRQuerySnapshot) { actual class DocumentChange(val ios: FIRDocumentChange) { actual val document: DocumentSnapshot - get() = DocumentSnapshot(ios.document) + get() = DocumentSnapshot(NativeDocumentSnapshot(ios.document)) actual val newIndex: Int get() = ios.newIndex.toInt() actual val oldIndex: Int @@ -386,16 +399,17 @@ actual class DocumentChange(val ios: FIRDocumentChange) { get() = ChangeType.values().first { it.ios == ios.type } } -actual class DocumentSnapshot(val ios: FIRDocumentSnapshot) : BaseDocumentSnapshot() { +@PublishedApi +internal actual class NativeDocumentSnapshot(val ios: FIRDocumentSnapshot) { actual val id get() = ios.documentID - actual val reference get() = DocumentReference(ios.reference) + actual val reference get() = NativeDocumentReference(ios.reference) - override fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = ios.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - override fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = ios.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) ?.mapValues { (_, value) -> value?.takeIf { it !is NSNull } @@ -414,6 +428,8 @@ actual class DocumentSnapshot(val ios: FIRDocumentSnapshot) : BaseDocumentSnapsh } } +val DocumentSnapshot.ios get() = native.ios + actual class SnapshotMetadata(val ios: FIRSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = ios.pendingWrites actual val isFromCache: Boolean get() = ios.fromCache diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt index d12bda859..84445fb4d 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -5,6 +5,6 @@ internal actual fun isSpecialValue(value: Any) = when(value) { is NativeFieldValue, is NativeGeoPoint, is NativeTimestamp, - is NativeDocumentReference -> true + is NativeDocumentReferenceType -> true else -> false } 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 439ea9ae1..5c8e47a66 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 @@ -71,19 +71,19 @@ actual class FirebaseFirestore(jsFirestore: Firestore) { var js: Firestore = jsFirestore private set - actual fun collection(collectionPath: String) = rethrow { CollectionReference(jsCollection(js, collectionPath)) } + actual fun collection(collectionPath: String) = rethrow { CollectionReference(NativeCollectionReference(jsCollection(js, collectionPath))) } actual fun collectionGroup(collectionId: String) = rethrow { Query(jsCollectionGroup(js, collectionId)) } - actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } + actual fun document(documentPath: String) = rethrow { DocumentReference(NativeDocumentReference(doc(js, documentPath))) } - actual fun batch() = rethrow { WriteBatch(writeBatch(js)) } + actual fun batch() = rethrow { WriteBatch(NativeWriteBatch(writeBatch(js))) } actual fun setLoggingEnabled(loggingEnabled: Boolean) = rethrow { setLogLevel( if(loggingEnabled) "error" else "silent") } actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - rethrow { jsRunTransaction(js, { GlobalScope.promise { Transaction(it).func() } } ).await() } + rethrow { jsRunTransaction(js, { GlobalScope.promise { Transaction(NativeTransaction(it)).func() } } ).await() } actual suspend fun clearPersistence() = rethrow { clearIndexedDbPersistence(js).await() } @@ -110,37 +110,38 @@ actual class FirebaseFirestore(jsFirestore: Firestore) { } } -val SetOptions.js: Json get() = when (this) { +internal val SetOptions.js: Json get() = when (this) { is SetOptions.Merge -> json("merge" to true) is SetOptions.Overwrite -> json("merge" to false) is SetOptions.MergeFields -> json("mergeFields" to fields.toTypedArray()) is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) } -actual class WriteBatch(val js: JsWriteBatch) : BaseWriteBatch() { +@PublishedApi +internal actual class NativeWriteBatch(val js: JsWriteBatch) { - override fun setEncoded( + actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): BaseWriteBatch = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } + ): NativeWriteBatch = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatch = rethrow { js.update(documentRef.js, encodedData) } .let { this } - override fun updateEncodedFieldsAndValues( + actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseWriteBatch = rethrow { + ): NativeWriteBatch = rethrow { encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } }.let { this } - override fun updateEncodedFieldPathsAndValues( + actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseWriteBatch = rethrow { + ): NativeWriteBatch = rethrow { encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } @@ -153,33 +154,36 @@ actual class WriteBatch(val js: JsWriteBatch) : BaseWriteBatch() { actual suspend fun commit() = rethrow { js.commit().await() } } -actual class Transaction(val js: JsTransaction) : BaseTransaction() { +val WriteBatch.js get() = native.js - override fun setEncoded( +@PublishedApi +internal actual class NativeTransaction(val js: JsTransaction) { + + actual fun setEncoded( documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions - ): BaseTransaction = rethrow { + ): NativeTransaction = rethrow { js.set(documentRef.js, encodedData, setOptions.js) } .let { this } - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransaction = rethrow { js.update(documentRef.js, encodedData) } .let { this } - override fun updateEncodedFieldsAndValues( + actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseTransaction = rethrow { + ): NativeTransaction = rethrow { encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } }.let { this } - override fun updateEncodedFieldPathsAndValues( + actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> - ): BaseTransaction = rethrow { + ): NativeTransaction = rethrow { encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> js.update(documentRef.js, field, value, *moreFieldsAndValues) } @@ -190,15 +194,17 @@ actual class Transaction(val js: JsTransaction) : BaseTransaction() { .let { this } actual suspend fun get(documentRef: DocumentReference) = - rethrow { DocumentSnapshot(js.get(documentRef.js).await()) } + rethrow { NativeDocumentSnapshot(js.get(documentRef.js).await()) } } +val Transaction.js get() = native.js + /** A class representing a platform specific Firebase DocumentReference. */ -actual typealias NativeDocumentReference = JsDocumentReference +actual typealias NativeDocumentReferenceType = JsDocumentReference -@Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { - val js: NativeDocumentReference = nativeValue +@PublishedApi +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + val js: NativeDocumentReferenceType = nativeValue actual val id: String get() = rethrow { js.id } @@ -206,32 +212,32 @@ actual class DocumentReference actual constructor(internal actual val nativeValu actual val path: String get() = rethrow { js.path } - actual val parent: CollectionReference - get() = rethrow { CollectionReference(js.parent) } + actual val parent: NativeCollectionReference + get() = rethrow { NativeCollectionReference(js.parent) } - actual fun collection(collectionPath: String) = rethrow { CollectionReference(jsCollection(js, collectionPath)) } + actual fun collection(collectionPath: String) = rethrow { NativeCollectionReference(jsCollection(js, collectionPath)) } - actual suspend fun get() = rethrow { DocumentSnapshot( getDoc(js).await()) } + actual suspend fun get() = rethrow { NativeDocumentSnapshot( getDoc(js).await()) } - actual val snapshots: Flow get() = snapshots() + actual val snapshots: Flow get() = snapshots() - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { val unsubscribe = onSnapshot( js, json("includeMetadataChanges" to includeMetadataChanges), - { trySend(DocumentSnapshot(it)) }, + { trySend(NativeDocumentSnapshot(it)) }, { close(errorToException(it)) } ) awaitClose { unsubscribe() } } - override suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) = rethrow { + actual suspend fun setEncoded(encodedData: Any, setOptions: SetOptions) = rethrow { setDoc(js, encodedData, setOptions.js).await() } - override suspend fun updateEncoded(encodedData: Any) = rethrow { jsUpdate(js, encodedData).await() } + actual suspend fun updateEncoded(encodedData: Any) = rethrow { jsUpdate(js, encodedData).await() } - override suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { rethrow { encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } ?.performUpdate { field, value, moreFieldsAndValues -> @@ -241,7 +247,7 @@ actual class DocumentReference actual constructor(internal actual val nativeValu } } - override suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { rethrow { encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } ?.performUpdate { field, value, moreFieldsAndValues -> @@ -250,15 +256,18 @@ actual class DocumentReference actual constructor(internal actual val nativeValu } } - override suspend fun delete() = rethrow { deleteDoc(js).await() } + actual suspend fun delete() = rethrow { deleteDoc(js).await() } override fun equals(other: Any?): Boolean = - this === other || other is DocumentReference && refEqual(nativeValue, other.nativeValue) + this === other || other is NativeDocumentReference && refEqual(nativeValue, other.nativeValue) override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = "DocumentReference(path=$path)" } -actual data class NativeQuery(val js: JsQuery) +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) { @@ -356,22 +365,25 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } } -actual class CollectionReference(override val js: JsCollectionReference) : BaseCollectionReference(NativeQuery(js)) { +@PublishedApi +internal actual class NativeCollectionReference(override val js: JsCollectionReference) : NativeQuery(js) { actual val path: String get() = rethrow { js.path } - actual val document get() = rethrow { DocumentReference(doc(js)) } + actual val document get() = rethrow { NativeDocumentReference(doc(js)) } - actual val parent get() = rethrow { js.parent?.let{DocumentReference(it)} } + actual val parent get() = rethrow { js.parent?.let{ NativeDocumentReference(it) } } - actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } + actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } - override suspend fun addEncoded(data: Any) = rethrow { - DocumentReference(addDoc(js, data).await()) + actual suspend fun addEncoded(data: Any) = rethrow { + NativeDocumentReference(addDoc(js, data).await()) } } +val CollectionReference.js get() = native.js + actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) @Suppress("EXTENSION_SHADOWED_BY_MEMBER") @@ -379,7 +391,7 @@ actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code actual class QuerySnapshot(val js: JsQuerySnapshot) { actual val documents - get() = js.docs.map { DocumentSnapshot(it) } + get() = js.docs.map { DocumentSnapshot(NativeDocumentSnapshot(it)) } actual val documentChanges get() = js.docChanges().map { DocumentChange(it) } actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) @@ -387,7 +399,7 @@ actual class QuerySnapshot(val js: JsQuerySnapshot) { actual class DocumentChange(val js: JsDocumentChange) { actual val document: DocumentSnapshot - get() = DocumentSnapshot(js.doc) + get() = DocumentSnapshot(NativeDocumentSnapshot(js.doc)) actual val newIndex: Int get() = js.newIndex actual val oldIndex: Int @@ -396,16 +408,17 @@ actual class DocumentChange(val js: JsDocumentChange) { get() = ChangeType.values().first { it.jsString == js.type } } -actual class DocumentSnapshot(val js: JsDocumentSnapshot) : BaseDocumentSnapshot() { +@PublishedApi +internal actual class NativeDocumentSnapshot(val js: JsDocumentSnapshot) { actual val id get() = rethrow { js.id } - actual val reference get() = rethrow { DocumentReference(js.ref) } + actual val reference get() = rethrow { NativeDocumentReference(js.ref) } - override fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { js.get(field, getTimestampsOptions(serverTimestampBehavior)) } - override fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { js.data(getTimestampsOptions(serverTimestampBehavior)) } @@ -417,6 +430,8 @@ actual class DocumentSnapshot(val js: JsDocumentSnapshot) : BaseDocumentSnapshot json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) } +val DocumentSnapshot.js get() = native.js + actual class SnapshotMetadata(val js: JsSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = js.hasPendingWrites actual val isFromCache: Boolean get() = js.fromCache From a2f103df0d7ea037bcc240dd5a6153e7b29441b0 Mon Sep 17 00:00:00 2001 From: Nicholas Bransby-Williams Date: Mon, 1 Apr 2024 13:00:28 +0300 Subject: [PATCH 27/27] Update README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 30ece95bf..daba85cfa 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,16 @@ The following libraries are available for the various Firebase products. | Service or Product | Gradle Dependency | API Coverage | |---------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [Authentication](https://firebase.google.com/docs/auth) | [`dev.gitlive:firebase-auth:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/1.11.1/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) | -| [Realtime Database](https://firebase.google.com/docs/database) | [`dev.gitlive:firebase-database:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-database/1.11.1/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) | -| [Cloud Firestore](https://firebase.google.com/docs/firestore) | [`dev.gitlive:firebase-firestore:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/1.11.1/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) | -| [Cloud Functions](https://firebase.google.com/docs/functions) | [`dev.gitlive:firebase-functions:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-functions/1.11.1/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt) | -| [Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) | [`dev.gitlive:firebase-messaging:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-messaging/1.11.1/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | -| [Cloud Storage](https://firebase.google.com/docs/storage) | [`dev.gitlive:firebase-storage:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-storage/1.11.1/pom) | [![40%](https://img.shields.io/badge/-40%25-orange?style=flat-square)](/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt) | -| [Installations](https://firebase.google.com/docs/projects/manage-installations) | [`dev.gitlive:firebase-installations:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-installations/1.11.1/pom) | [![90%](https://img.shields.io/badge/-90%25-green?style=flat-square)](/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt) | -| [Remote Config](https://firebase.google.com/docs/remote-config) | [`dev.gitlive:firebase-config:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-config/1.11.1/pom) | [![20%](https://img.shields.io/badge/-20%25-orange?style=flat-square)](/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt) | -| [Performance](https://firebase.google.com/docs/perf-mon) | [`dev.gitlive:firebase-perf:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-perf/1.11.1/pom) | [![1%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt) | -| [Crashlytics](https://firebase.google.com/docs/crashlytics) | [`dev.gitlive:firebase-crashlytics:1.11.1`](https://search.maven.org/artifact/dev.gitlive/firebase-crashlytics/1.11.1/pom) | [![80%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt) | +| [Authentication](https://firebase.google.com/docs/auth) | [`dev.gitlive:firebase-auth:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/1.12.0/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) | +| [Realtime Database](https://firebase.google.com/docs/database) | [`dev.gitlive:firebase-database:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-database/1.12.0/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) | +| [Cloud Firestore](https://firebase.google.com/docs/firestore) | [`dev.gitlive:firebase-firestore:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/1.12.0/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) | +| [Cloud Functions](https://firebase.google.com/docs/functions) | [`dev.gitlive:firebase-functions:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-functions/1.12.0/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt) | +| [Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) | [`dev.gitlive:firebase-messaging:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-messaging/1.12.0/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | +| [Cloud Storage](https://firebase.google.com/docs/storage) | [`dev.gitlive:firebase-storage:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-storage/1.12.0/pom) | [![40%](https://img.shields.io/badge/-40%25-orange?style=flat-square)](/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt) | +| [Installations](https://firebase.google.com/docs/projects/manage-installations) | [`dev.gitlive:firebase-installations:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-installations/1.12.0/pom) | [![90%](https://img.shields.io/badge/-90%25-green?style=flat-square)](/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt) | +| [Remote Config](https://firebase.google.com/docs/remote-config) | [`dev.gitlive:firebase-config:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-config/1.12.0/pom) | [![20%](https://img.shields.io/badge/-20%25-orange?style=flat-square)](/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt) | +| [Performance](https://firebase.google.com/docs/perf-mon) | [`dev.gitlive:firebase-perf:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-perf/1.12.0/pom) | [![1%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt) | +| [Crashlytics](https://firebase.google.com/docs/crashlytics) | [`dev.gitlive:firebase-crashlytics:1.12.0`](https://search.maven.org/artifact/dev.gitlive/firebase-crashlytics/1.12.0/pom) | [![80%](https://img.shields.io/badge/-1%25-orange?style=flat-square)](/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt) | Is the Firebase library or API you need missing? [Create an issue](https://github.com/GitLiveApp/firebase-kotlin-sdk/issues/new?labels=API+coverage&template=increase-api-coverage.md&title=Add+%5Bclass+name%5D.%5Bfunction+name%5D+to+%5Blibrary+name%5D+for+%5Bplatform+names%5D) to request additional API coverage or be awesome and [submit a PR](https://github.com/GitLiveApp/firebase-kotlin-sdk/fork)