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 cfeda0c23..ef16bbf5a 100644 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt @@ -67,7 +67,7 @@ class EncodersTest { val decoded = decode(TestData.serializer(), mapOf("map" to mapOf("key" to "value"), "nullableBool" to null)) assertNull(decoded.nullableBool) } -} + @Test fun testEncodeDecodedSealedClass() { val test = SealedClass.Test("Foo") diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt index 706246004..aef7ef102 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt @@ -1,7 +1,7 @@ package dev.gitlive.firebase.firestore actual class FirebaseDocumentReferenceEncoder actual constructor() { - actual fun encode(value: DocumentReference): Any { + actual fun encode(value: DocumentReferenceWrapper): Any { return value.android } } 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 de3905a65..2410607d5 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -28,33 +28,35 @@ actual val Firebase.firestore get() = actual fun Firebase.firestore(app: FirebaseApp) = FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance(app.android)) -actual class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) { +actual class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) : IFirebaseFirestore { - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) + override fun collection(collectionPath: String) = + CollectionReferenceWrapper(android.collection(collectionPath)) - actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) + override fun document(documentPath: String) = + DocumentReferenceWrapper(android.document(documentPath)) - actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId)) + override fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId)) - actual fun batch() = WriteBatch(android.batch()) + override fun batch() = WriteBatch(android.batch()) - actual fun setLoggingEnabled(loggingEnabled: Boolean) = + override fun setLoggingEnabled(loggingEnabled: Boolean) = com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) - actual suspend fun runTransaction(func: suspend Transaction.() -> T) = + override suspend fun runTransaction(func: suspend Transaction.() -> T) = android.runTransaction { runBlocking { Transaction(it).func() } }.await() - actual suspend fun clearPersistence() = + override suspend fun clearPersistence() = android.clearPersistence().await().run { } - actual fun useEmulator(host: String, port: Int) { + override fun useEmulator(host: String, port: Int) { android.useEmulator(host, port) android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings.Builder() .setPersistenceEnabled(false) .build() } - actual fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { + override 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 } @@ -63,10 +65,10 @@ actual class FirebaseFirestore(val android: com.google.firebase.firestore.Fireba }.build() } - actual suspend fun disableNetwork() = + override suspend fun disableNetwork() = android.disableNetwork().await().run { } - actual suspend fun enableNetwork() = + override suspend fun enableNetwork() = android.enableNetwork().await().run { } } @@ -74,44 +76,44 @@ 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)!!) + true -> android.set((documentRef as DocumentReferenceWrapper).android, encode(data, encodeDefaults)!!, SetOptions.merge()) + false -> android.set((documentRef as DocumentReferenceWrapper).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)) + android.set((documentRef as DocumentReferenceWrapper).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 })) + android.set((documentRef as DocumentReferenceWrapper).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)!!) + true -> android.set((documentRef as DocumentReferenceWrapper).android, encode(strategy, data, encodeDefaults)!!, SetOptions.merge()) + false -> android.set((documentRef as DocumentReferenceWrapper).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)) + android.set((documentRef as DocumentReferenceWrapper).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 })) + android.set((documentRef as DocumentReferenceWrapper).android, encode(strategy, data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) .let { 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 } + android.update((documentRef as DocumentReferenceWrapper).android, encode(data, encodeDefaults) 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 } + android.update((documentRef as DocumentReferenceWrapper).android, encode(strategy, data, encodeDefaults) as Map).let { this } @JvmName("updateFields") actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } ?.map { (field, value) -> field to encode(value, true) } - ?.let { encoded -> android.update(documentRef.android, encoded.toMap()) } + ?.let { encoded -> android.update((documentRef as DocumentReferenceWrapper).android, encoded.toMap()) } .let { this } @JvmName("updateFieldPaths") @@ -120,7 +122,7 @@ actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) { ?.map { (field, value) -> field.android to encode(value, true) } ?.let { encoded -> android.update( - documentRef.android, + (documentRef as DocumentReferenceWrapper).android, encoded.first().first, encoded.first().second, *encoded.drop(1) @@ -131,7 +133,7 @@ actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) { .let { this } actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } + android.delete((documentRef as DocumentReferenceWrapper).android).let { this } actual suspend fun commit() = android.commit().await().run { Unit } @@ -140,16 +142,16 @@ actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) { 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)!!) + true -> android.set((documentRef as DocumentReferenceWrapper).android, encode(data, encodeDefaults)!!, SetOptions.merge()) + false -> android.set((documentRef as DocumentReferenceWrapper).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)) + android.set((documentRef as DocumentReferenceWrapper).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 })) + android.set((documentRef as DocumentReferenceWrapper).android, encode(data, encodeDefaults)!!, SetOptions.mergeFieldPaths(mergeFieldPaths.map { it.android })) .let { this } actual fun set( @@ -159,31 +161,31 @@ actual class Transaction(val android: com.google.firebase.firestore.Transaction) 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)!!) + true -> android.set((documentRef as DocumentReferenceWrapper).android, encode(strategy, data, encodeDefaults)!!, SetOptions.merge()) + false -> android.set((documentRef as DocumentReferenceWrapper).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)) + android.set((documentRef as DocumentReferenceWrapper).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 })) + android.set((documentRef as DocumentReferenceWrapper).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 } + android.update((documentRef as DocumentReferenceWrapper).android, encode(data, encodeDefaults) 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 } + android.update((documentRef as DocumentReferenceWrapper).android, encode(strategy, data, encodeDefaults) as Map).let { this } @JvmName("updateFields") actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } ?.map { (field, value) -> field to encode(value, true) } - ?.let { encoded -> android.update(documentRef.android, encoded.toMap()) } + ?.let { encoded -> android.update((documentRef as DocumentReferenceWrapper).android, encoded.toMap()) } .let { this } @JvmName("updateFieldPaths") @@ -192,7 +194,7 @@ actual class Transaction(val android: com.google.firebase.firestore.Transaction) ?.map { (field, value) -> field.android to encode(value, true) } ?.let { encoded -> android.update( - documentRef.android, + (documentRef as DocumentReferenceWrapper).android, encoded.first().first, encoded.first().second, *encoded.drop(1) @@ -202,21 +204,22 @@ actual class Transaction(val android: com.google.firebase.firestore.Transaction) }.let { this } actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } + android.delete((documentRef as DocumentReferenceWrapper).android).let { this } actual suspend fun get(documentRef: DocumentReference) = - DocumentSnapshot(android.get(documentRef.android)) + DocumentSnapshot(android.get((documentRef as DocumentReferenceWrapper).android)) } -actual class DocumentReference(val android: com.google.firebase.firestore.DocumentReference) { +actual class DocumentReferenceWrapper(val android: com.google.firebase.firestore.DocumentReference) : DocumentReference { - actual val id: String + override val id: String get() = android.id - actual val path: String + override val path: String get() = android.path - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) + override fun collection(collectionPath: String) = + CollectionReferenceWrapper(android.collection(collectionPath)) actual suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean) = when(merge) { true -> android.set(encode(data, encodeDefaults)!!, SetOptions.merge()) @@ -231,16 +234,16 @@ actual class DocumentReference(val android: com.google.firebase.firestore.Docume 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) { + override 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) = + override 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) = + override 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 } @@ -249,19 +252,26 @@ actual class DocumentReference(val android: com.google.firebase.firestore.Docume android.update(encode(data, encodeDefaults) as Map).await().run { Unit } @Suppress("UNCHECKED_CAST") - actual suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = + override 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) = - fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } - ?.map { (field, value) -> field to encode(value, true) } - ?.let { encoded -> android.update(encoded.toMap()) } + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("updateFieldsByKey") + override suspend fun update(vararg fieldsAndValues: Pair) = + android.takeUnless { fieldsAndValues.isEmpty() } + ?.update( + fieldsAndValues[0].first, + fieldsAndValues[0].second, + *fieldsAndValues.drop(1).flatMap { (field, value) -> + listOf(field, value?.let { encode(value, true) }) + }.toTypedArray() + ) ?.await() .run { Unit } - @JvmName("updateFieldPaths") - actual suspend fun update(vararg fieldsAndValues: Pair) = + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("updateFieldsByFieldPath") + override suspend fun update(vararg fieldsAndValues: Pair) = fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } ?.map { (field, value) -> field.android to encode(value, true) } ?.let { encoded -> @@ -271,18 +281,16 @@ actual class DocumentReference(val android: com.google.firebase.firestore.Docume *encoded.drop(1) .flatMap { (field, value) -> listOf(field, value) } .toTypedArray() - ) - } - ?.await() - .run { Unit } + ).await() + }.run { Unit } - actual suspend fun delete() = + override suspend fun delete() = android.delete().await().run { Unit } - actual suspend fun get() = + override suspend fun get() = DocumentSnapshot(android.get().await()) - actual val snapshots get() = callbackFlow { + override val snapshots get() = callbackFlow { val listener = android.addSnapshotListener { snapshot, exception -> snapshot?.let { safeOffer(DocumentSnapshot(snapshot)) } exception?.let { close(exception) } @@ -308,8 +316,8 @@ actual open class Query(open val android: com.google.firebase.firestore.Query) { internal actual fun _where(field: String, equalTo: Any?) = Query(android.whereEqualTo(field, equalTo)) internal actual fun _where(path: FieldPath, equalTo: Any?) = Query(android.whereEqualTo(path.android, equalTo)) - internal actual fun _where(field: String, equalTo: DocumentReference) = Query(android.whereEqualTo(field, equalTo.android)) - internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(android.whereEqualTo(path.android, equalTo.android)) + internal actual fun _where(field: String, equalTo: DocumentReference) = Query(android.whereEqualTo(field, (equalTo as DocumentReferenceWrapper).android)) + internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(android.whereEqualTo(path.android, (equalTo as DocumentReferenceWrapper).android)) internal actual fun _where( field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, @@ -370,22 +378,22 @@ 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 CollectionReferenceWrapper(override val android: com.google.firebase.firestore.CollectionReference) : Query(android), CollectionReference { - actual val path: String + override val path: String get() = android.path - actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) + override fun document(documentPath: String) = DocumentReferenceWrapper(android.document(documentPath)) - actual fun document() = DocumentReference(android.document()) + override fun document() = DocumentReferenceWrapper(android.document()) - actual suspend inline fun add(data: T, encodeDefaults: Boolean) = - DocumentReference(android.add(encode(data, encodeDefaults)!!).await()) + actual suspend inline fun add(data: T, encodeDefaults: Boolean): DocumentReference = + DocumentReferenceWrapper(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()) + override suspend fun add(data: T, strategy: SerializationStrategy, encodeDefaults: Boolean) = + DocumentReferenceWrapper(android.add(encode(strategy, data, encodeDefaults)!!).await()) + override suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = + DocumentReferenceWrapper(android.add(encode(strategy, data, encodeDefaults)!!).await()) } actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException @@ -417,7 +425,8 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC actual class DocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) { actual val id get() = android.id - actual val reference get() = DocumentReference(android.reference) + actual val reference: DocumentReference + get() = DocumentReferenceWrapper(android.reference) actual inline fun data() = decode(value = android.data) diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceSerializer.kt index a3ec4d717..e7de11858 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceSerializer.kt @@ -10,10 +10,10 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder expect class FirebaseDocumentReferenceEncoder() { - fun encode(value: DocumentReference): Any + fun encode(value: DocumentReferenceWrapper): Any } -class FirebaseDocumentReferenceSerializer : KSerializer { +class FirebaseDocumentReferenceSerializer : KSerializer { override val descriptor = object : SerialDescriptor { val keys = listOf("path") @@ -27,17 +27,17 @@ class FirebaseDocumentReferenceSerializer : KSerializer { override fun isElementOptional(index: Int) = false } - override fun serialize(encoder: Encoder, value: DocumentReference) { + override fun serialize(encoder: Encoder, value: DocumentReferenceWrapper) { val objectEncoder = encoder.beginStructure(descriptor) as FirebaseCompositeEncoder val documentReferenceEncoder = FirebaseDocumentReferenceEncoder() objectEncoder.encodeObject(descriptor, 0, documentReferenceEncoder.encode(value)) objectEncoder.endStructure(descriptor) } - override fun deserialize(decoder: Decoder): DocumentReference { + override fun deserialize(decoder: Decoder): DocumentReferenceWrapper { val objectDecoder = decoder.beginStructure(descriptor) as FirebaseCompositeDecoder val path = objectDecoder.decodeStringElement(descriptor, 0) objectDecoder.endStructure(descriptor) - return Firebase.firestore.document(path) + return Firebase.firestore.document(path) as DocumentReferenceWrapper } } diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FirebaseReference.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FirebaseReference.kt index af047b767..c470d7db7 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FirebaseReference.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FirebaseReference.kt @@ -1,11 +1,11 @@ package dev.gitlive.firebase.firestore sealed class FirebaseReference { - data class Value(val value: DocumentReference) : FirebaseReference() + data class Value(val value: DocumentReferenceWrapper) : FirebaseReference() object ServerDelete : FirebaseReference() } -val FirebaseReference.reference: DocumentReference? get() = when (this) { +val FirebaseReference.reference: DocumentReferenceWrapper? get() = when (this) { is FirebaseReference.Value -> value is FirebaseReference.ServerDelete -> null } diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/ReferenceSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/ReferenceSerializer.kt index aa3495cc9..41c158fbf 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/ReferenceSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/ReferenceSerializer.kt @@ -49,7 +49,7 @@ class FirebaseReferenceNullableSerializer : AbstractFirebaseReferenceSerializer< val objectDecoder = decoder.beginStructure(descriptor) as FirebaseCompositeDecoder return try { val path = objectDecoder.decodeStringElement(descriptor, 0) - FirebaseReference.Value(Firebase.firestore.document(path)) + FirebaseReference.Value(Firebase.firestore.document(path) as DocumentReferenceWrapper) } catch (exception: Exception) { null } finally { @@ -68,6 +68,6 @@ class FirebaseReferenceSerializer : AbstractFirebaseReferenceSerializer @@ -123,34 +125,48 @@ expect class DocumentReference { 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) + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("updateFieldsByKey") suspend fun update(vararg fieldsAndValues: Pair) + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("updateFieldsByFieldPath") suspend fun update(vararg fieldsAndValues: Pair) suspend fun delete() } -expect class CollectionReference : Query { +expect class DocumentReferenceWrapper : DocumentReference { + + 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 inline fun update(data: T, encodeDefaults: Boolean = true) +} + +interface CollectionReference { val path: String fun document(documentPath: String): DocumentReference fun document(): 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 } +expect class CollectionReferenceWrapper : Query, CollectionReference { + suspend inline fun add(data: T, encodeDefaults: Boolean = true): DocumentReference +} + expect class FirebaseFirestoreException : FirebaseException @Suppress("EXTENSION_SHADOWED_BY_MEMBER") diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt index 9ead7dea4..fa9e1ba08 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt @@ -1,7 +1,7 @@ package dev.gitlive.firebase.firestore actual class FirebaseDocumentReferenceEncoder actual constructor() { - actual fun encode(value: DocumentReference): Any { + actual fun encode(value: DocumentReferenceWrapper): Any { return value.ios } } 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 f8074e176..3d1b09d0b 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 @@ -24,26 +24,26 @@ actual fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore { } @Suppress("UNCHECKED_CAST") -actual class FirebaseFirestore(val ios: FIRFirestore) { +actual class FirebaseFirestore(val ios: FIRFirestore) : IFirebaseFirestore { - actual fun collection(collectionPath: String) = CollectionReference(ios.collectionWithPath(collectionPath)) + override fun collection(collectionPath: String) = CollectionReferenceWrapper(ios.collectionWithPath(collectionPath)) - actual fun document(documentPath: String) = DocumentReference(ios.documentWithPath(documentPath)) + override fun document(documentPath: String) = DocumentReferenceWrapper(ios.documentWithPath(documentPath)) - actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId)) + override fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId)) - actual fun batch() = WriteBatch(ios.batch()) + override fun batch() = WriteBatch(ios.batch()) - actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = + override fun setLoggingEnabled(loggingEnabled: Boolean): Unit = FIRFirestore.enableLogging(loggingEnabled) - actual suspend fun runTransaction(func: suspend Transaction.() -> T) = + override suspend fun runTransaction(func: suspend Transaction.() -> T) = awaitResult { ios.runTransactionWithBlock({ transaction, _ -> runBlocking { Transaction(transaction!!).func() } }, it) } as T - actual suspend fun clearPersistence() = + override suspend fun clearPersistence() = await { ios.clearPersistenceWithCompletion(it) } - actual fun useEmulator(host: String, port: Int) { + override fun useEmulator(host: String, port: Int) { ios.settings = ios.settings.apply { this.host = "$host:$port" persistenceEnabled = false @@ -51,7 +51,7 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { } } - actual fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { + override fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { ios.settings = FIRFirestoreSettings().also { settings -> persistenceEnabled?.let { settings.persistenceEnabled = it } sslEnabled?.let { settings.sslEnabled = it } @@ -60,11 +60,11 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { } } - actual suspend fun disableNetwork() { + override suspend fun disableNetwork() { await { ios.disableNetworkWithCompletion(it) } } - actual suspend fun enableNetwork() { + override suspend fun enableNetwork() { await { ios.enableNetworkWithCompletion(it) } } } @@ -73,43 +73,43 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { 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 } + ios.setData(encode(data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(strategy, data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(strategy, data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(strategy, data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).ios, mergeFieldPaths.map { it.ios }).let { this } actual inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = - ios.updateData(encode(data, encodeDefaults) as Map, documentRef.ios).let { this } + ios.updateData(encode(data, encodeDefaults) as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.updateData(encode(strategy, data, encodeDefaults) as Map, (documentRef as DocumentReferenceWrapper).ios).let { this } actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = ios.updateData( fieldsAndValues.associate { (field, value) -> field to encode(value, true) }, - documentRef.ios + (documentRef as DocumentReferenceWrapper).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 + (documentRef as DocumentReferenceWrapper).ios ).let { this } actual fun delete(documentRef: DocumentReference) = - ios.deleteDocument(documentRef.ios).let { this } + ios.deleteDocument((documentRef as DocumentReferenceWrapper).ios).let { this } actual suspend fun commit() = await { ios.commitWithCompletion(it) } @@ -119,85 +119,87 @@ actual class WriteBatch(val ios: FIRWriteBatch) { 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 } + ios.setData(encode(data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(strategy, data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(strategy, data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.setData(encode(strategy, data, encodeDefaults)!! as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.updateData(encode(data, encodeDefaults) as Map, (documentRef as DocumentReferenceWrapper).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 } + ios.updateData(encode(strategy, data, encodeDefaults) as Map, (documentRef as DocumentReferenceWrapper).ios).let { this } actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = ios.updateData( fieldsAndValues.associate { (field, value) -> field to encode(value, true) }, - documentRef.ios + (documentRef as DocumentReferenceWrapper).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 + (documentRef as DocumentReferenceWrapper).ios ).let { this } actual fun delete(documentRef: DocumentReference) = - ios.deleteDocument(documentRef.ios).let { this } + ios.deleteDocument((documentRef as DocumentReferenceWrapper).ios).let { this } actual suspend fun get(documentRef: DocumentReference) = - throwError { DocumentSnapshot(ios.getDocument(documentRef.ios, it)!!) } + throwError { DocumentSnapshot(ios.getDocument((documentRef as DocumentReferenceWrapper).ios, it)!!) } } @Suppress("UNCHECKED_CAST") -actual class DocumentReference(val ios: FIRDocumentReference) { +actual class DocumentReferenceWrapper(val ios: FIRDocumentReference) : DocumentReference { - actual val id: String + override val id: String get() = ios.documentID - actual val path: String + override val path: String get() = ios.path - actual fun collection(collectionPath: String) = CollectionReference(ios.collectionWithPath(collectionPath)) + override fun collection(collectionPath: String) = CollectionReferenceWrapper(ios.collectionWithPath(collectionPath)) actual suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean) = await { ios.setData(encode(data, encodeDefaults)!! as Map, merge, it) } + .run { Unit } actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = await { ios.setData(encode(data, encodeDefaults)!! as Map, mergeFields.asList(), it) } + .run { Unit } 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) } - - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = + .run { Unit } + override suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = await { ios.setData(encode(strategy, data, encodeDefaults)!! as Map, merge, it) } - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = + override 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 suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = + override 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) } actual suspend inline fun update(data: T, encodeDefaults: Boolean) = await { ios.updateData(encode(data, encodeDefaults) as Map, it) } - actual suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = + override suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = await { ios.updateData(encode(strategy, data, encodeDefaults) as Map, it) } - actual suspend fun update(vararg fieldsAndValues: Pair) = + override suspend fun update(vararg fieldsAndValues: Pair) = await { block -> ios.updateData( fieldsAndValues.associate { (field, value) -> field to encode(value, true) }, @@ -205,7 +207,7 @@ actual class DocumentReference(val ios: FIRDocumentReference) { ) } - actual suspend fun update(vararg fieldsAndValues: Pair) = + override suspend fun update(vararg fieldsAndValues: Pair) = await { block -> ios.updateData( fieldsAndValues.associate { (path, value) -> path.ios to encode(value, true) }, @@ -213,13 +215,13 @@ actual class DocumentReference(val ios: FIRDocumentReference) { ) } - actual suspend fun delete() = + override suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } - actual suspend fun get() = + override suspend fun get() = DocumentSnapshot(awaitResult { ios.getDocumentWithCompletion(it) }) - actual val snapshots get() = callbackFlow { + override val snapshots get() = callbackFlow { val listener = ios.addSnapshotListener { snapshot, error -> snapshot?.let { safeOffer(DocumentSnapshot(snapshot)) } error?.let { close(error.toException()) } @@ -245,8 +247,8 @@ actual open class Query(open val ios: FIRQuery) { internal actual fun _where(field: String, equalTo: Any?) = Query(ios.queryWhereField(field, isEqualTo = equalTo!!)) internal actual fun _where(path: FieldPath, equalTo: Any?) = Query(ios.queryWhereFieldPath(path.ios, isEqualTo = equalTo!!)) - internal actual fun _where(field: String, equalTo: DocumentReference) = Query(ios.queryWhereField(field, isEqualTo = equalTo.ios)) - internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(ios.queryWhereFieldPath(path.ios, isEqualTo = equalTo.ios)) + internal actual fun _where(field: String, equalTo: DocumentReference) = Query(ios.queryWhereField(field, isEqualTo = (equalTo as DocumentReferenceWrapper).ios)) + internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(ios.queryWhereFieldPath(path.ios, isEqualTo = (equalTo as DocumentReferenceWrapper).ios)) internal actual fun _where( field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, @@ -305,22 +307,22 @@ actual open class Query(open val ios: FIRQuery) { } @Suppress("UNCHECKED_CAST") -actual class CollectionReference(override val ios: FIRCollectionReference) : Query(ios) { +actual class CollectionReferenceWrapper(override val ios: FIRCollectionReference) : Query(ios), CollectionReference { - actual val path: String + override val path: String get() = ios.path - actual fun document(documentPath: String) = DocumentReference(ios.documentWithPath(documentPath)) + override fun document(documentPath: String) = DocumentReferenceWrapper(ios.documentWithPath(documentPath)) - actual fun document() = DocumentReference(ios.documentWithAutoID()) + override fun document() = DocumentReferenceWrapper(ios.documentWithAutoID()) - 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, encodeDefaults: Boolean): DocumentReference = + DocumentReferenceWrapper(await { ios.addDocumentWithData(encode(data, encodeDefaults) 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) }) + override suspend fun add(data: T, strategy: SerializationStrategy, encodeDefaults: Boolean): DocumentReference = + DocumentReferenceWrapper(await { ios.addDocumentWithData(encode(strategy, data, encodeDefaults) as Map, it) }) + override suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): DocumentReference = + DocumentReferenceWrapper(await { ios.addDocumentWithData(encode(strategy, data, encodeDefaults) as Map, it) }) } actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) @@ -406,7 +408,8 @@ actual class DocumentSnapshot(val ios: FIRDocumentSnapshot) { actual val id get() = ios.documentID - actual val reference get() = DocumentReference(ios.reference) + actual val reference: DocumentReference + get() = DocumentReferenceWrapper(ios.reference) actual inline fun data() = decode(value = ios.data()) diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt index bb39e2f24..e32b9fd68 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FirebaseDocumentReferenceEncoder.kt @@ -1,7 +1,7 @@ package dev.gitlive.firebase.firestore actual class FirebaseDocumentReferenceEncoder actual constructor() { - actual fun encode(value: DocumentReference): Any { + actual fun encode(value: DocumentReferenceWrapper): Any { return value.js } } 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 24d0f2cca..8f193ac5e 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 @@ -8,40 +8,137 @@ import dev.gitlive.firebase.* import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.await import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.promise import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy import kotlin.js.json +fun errorToException(e: dynamic) = (e?.code ?: e?.message ?: "") + .toString() + .toLowerCase() + .let { + when { + "cancelled" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.CANCELLED + ) + "invalid-argument" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.INVALID_ARGUMENT + ) + "deadline-exceeded" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.DEADLINE_EXCEEDED + ) + "not-found" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.NOT_FOUND + ) + "already-exists" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.ALREADY_EXISTS + ) + "permission-denied" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.PERMISSION_DENIED + ) + "resource-exhausted" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.RESOURCE_EXHAUSTED + ) + "failed-precondition" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.FAILED_PRECONDITION + ) + "aborted" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.ABORTED + ) + "out-of-range" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.OUT_OF_RANGE + ) + "unimplemented" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.UNIMPLEMENTED + ) + "internal" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.INTERNAL + ) + "unavailable" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.UNAVAILABLE + ) + "data-loss" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.DATA_LOSS + ) + "unauthenticated" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.UNAUTHENTICATED + ) + "unknown" in it -> FirebaseFirestoreException( + e, + FirestoreExceptionCode.UNKNOWN + ) + else -> { + println("Unknown error code in ${JSON.stringify(e)}") + FirebaseFirestoreException( + e, + FirestoreExceptionCode.UNKNOWN + ) + } + } + } + +inline fun rethrow(function: () -> R): R { + try { + return function() + } catch (e: Exception) { + throw e + } catch (e: dynamic) { + throw errorToException(e) + } +} + +inline fun T.rethrow(function: T.() -> R): R = + dev.gitlive.firebase.firestore.rethrow { function() } + actual val Firebase.firestore get() = rethrow { dev.gitlive.firebase.firestore; FirebaseFirestore(firebase.firestore()) } actual fun Firebase.firestore(app: FirebaseApp) = rethrow { dev.gitlive.firebase.firestore; FirebaseFirestore(firebase.app().firestore()) } -actual class FirebaseFirestore(val js: firebase.firestore.Firestore) { +actual class FirebaseFirestore(val js: firebase.firestore.Firestore) : IFirebaseFirestore { - actual fun collection(collectionPath: String) = rethrow { CollectionReference(js.collection(collectionPath)) } + override fun collection(collectionPath: String) = rethrow { + CollectionReferenceWrapper(js.collection(collectionPath)) + } - actual fun document(documentPath: String) = rethrow { DocumentReference(js.doc(documentPath)) } + override fun document(documentPath: String) = rethrow { + DocumentReferenceWrapper(js.doc(documentPath)) + } - actual fun collectionGroup(collectionId: String) = rethrow { Query(js.collectionGroup(collectionId)) } + override fun collectionGroup(collectionId: String) = rethrow { Query(js.collectionGroup(collectionId)) } - actual fun batch() = rethrow { WriteBatch(js.batch()) } + override fun batch() = rethrow { WriteBatch(js.batch()) } - actual fun setLoggingEnabled(loggingEnabled: Boolean) = + override fun setLoggingEnabled(loggingEnabled: Boolean) = rethrow { firebase.firestore.setLogLevel( if(loggingEnabled) "error" else "silent") } - actual suspend fun runTransaction(func: suspend Transaction.() -> T) = + override suspend fun runTransaction(func: suspend Transaction.() -> T) = rethrow { js.runTransaction { GlobalScope.promise { Transaction(it).func() } }.await() } - actual suspend fun clearPersistence() = + override suspend fun clearPersistence() = rethrow { js.clearPersistence().await() } - actual fun useEmulator(host: String, port: Int) = rethrow { js.useEmulator(host, port) } + override fun useEmulator(host: String, port: Int) = rethrow { js.useEmulator(host, port) } - actual fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { + override fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { if(persistenceEnabled == true) js.enablePersistence() js.settings(json().apply { @@ -51,11 +148,11 @@ actual class FirebaseFirestore(val js: firebase.firestore.Firestore) { }) } - actual suspend fun disableNetwork() { + override suspend fun disableNetwork() { rethrow { js.disableNetwork().await() } } - actual suspend fun enableNetwork() { + override suspend fun enableNetwork() { rethrow { js.enableNetwork().await() } } } @@ -63,41 +160,41 @@ actual class FirebaseFirestore(val js: firebase.firestore.Firestore) { actual class WriteBatch(val js: firebase.firestore.WriteBatch) { actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean) = - rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("merge" to merge)) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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)) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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())) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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)) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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)) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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())) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).js, encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())) } .let { this } actual inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = - rethrow { js.update(documentRef.js, encode(data, encodeDefaults)!!) } + rethrow { js.update((documentRef as DocumentReferenceWrapper).js, encode(data, encodeDefaults)!!) } .let { this } actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { js.update(documentRef.js, encode(strategy, data, encodeDefaults)!!) } + rethrow { js.update((documentRef as DocumentReferenceWrapper).js, encode(strategy, data, encodeDefaults)!!) } .let { this } actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = rethrow { fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } ?.map { (field, value) -> field to encode(value, true) } - ?.let { encoded -> js.update(documentRef.js, encoded.toMap()) } + ?.let { encoded -> js.update((documentRef as DocumentReferenceWrapper).js, encoded.toMap()) } }.let { this } actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = rethrow { @@ -105,7 +202,7 @@ actual class WriteBatch(val js: firebase.firestore.WriteBatch) { ?.map { (field, value) -> field.js to encode(value, true) } ?.let { encoded -> js.update( - documentRef.js, + (documentRef as DocumentReferenceWrapper).js, encoded.first().first, encoded.first().second, *encoded.drop(1) @@ -116,51 +213,50 @@ actual class WriteBatch(val js: firebase.firestore.WriteBatch) { }.let { this } actual fun delete(documentRef: DocumentReference) = - rethrow { js.delete(documentRef.js) } + rethrow { js.delete((documentRef as DocumentReferenceWrapper).js) } .let { this } actual suspend fun commit() = rethrow { js.commit().await() } - } actual class Transaction(val js: firebase.firestore.Transaction) { actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean) = - rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("merge" to merge)) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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)) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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())) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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)) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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)) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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())) } + rethrow { js.set((documentRef as DocumentReferenceWrapper).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)!!) } + rethrow { js.update((documentRef as DocumentReferenceWrapper).js, encode(data, encodeDefaults)!!) } .let { this } actual fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { js.update(documentRef.js, encode(strategy, data, encodeDefaults)!!) } + rethrow { js.update((documentRef as DocumentReferenceWrapper).js, encode(strategy, data, encodeDefaults)!!) } .let { this } actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = rethrow { fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } ?.map { (field, value) -> field to encode(value, true) } - ?.let { encoded -> js.update(documentRef.js, encoded.toMap()) } + ?.let { encoded -> js.update((documentRef as DocumentReferenceWrapper).js, encoded.toMap()) } }.let { this } actual fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair) = rethrow { @@ -168,7 +264,7 @@ actual class Transaction(val js: firebase.firestore.Transaction) { ?.map { (field, value) -> field.js to encode(value, true) } ?.let { encoded -> js.update( - documentRef.js, + (documentRef as DocumentReferenceWrapper).js, encoded.first().first, encoded.first().second, *encoded.drop(1) @@ -179,93 +275,144 @@ actual class Transaction(val js: firebase.firestore.Transaction) { }.let { this } actual fun delete(documentRef: DocumentReference) = - rethrow { js.delete(documentRef.js) } + rethrow { js.delete((documentRef as DocumentReferenceWrapper).js) } .let { this } actual suspend fun get(documentRef: DocumentReference) = - rethrow { DocumentSnapshot(js.get(documentRef.js).await()) } + rethrow { DocumentSnapshot(js.get((documentRef as DocumentReferenceWrapper).js).await()) } } -actual class DocumentReference(val js: firebase.firestore.DocumentReference) { +actual class DocumentReferenceWrapper(val js: firebase.firestore.DocumentReference) : DocumentReference { - actual val id: String + override val id: String get() = rethrow { js.id } - actual val path: String + override val path: String get() = rethrow { js.path } - actual fun collection(collectionPath: String) = rethrow { CollectionReference(js.collection(collectionPath)) } + override fun collection(collectionPath: String): CollectionReference = + rethrow { CollectionReferenceWrapper(js.collection(collectionPath)) } actual suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean) = rethrow { js.set(encode(data, encodeDefaults)!!, json("merge" to merge)).await() } - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { js.set(encode(data, encodeDefaults)!!, json("mergeFields" to mergeFields)).await() } + actual suspend inline fun set( + data: T, + encodeDefaults: Boolean, + vararg mergeFields: String + ) = + rethrow { + js.set(encode(data, encodeDefaults)!!, json("mergeFields" to mergeFields)).await() + } - actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { js.set(encode(data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())).await() } + actual suspend inline fun set( + data: T, + encodeDefaults: Boolean, + vararg mergeFieldPaths: FieldPath + ) = + rethrow { + js.set( + encode(data, encodeDefaults)!!, + json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray()) + ).await() + } - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = + override suspend fun set( + strategy: SerializationStrategy, + data: T, + encodeDefaults: Boolean, + merge: Boolean + ) = rethrow { js.set(encode(strategy, data, encodeDefaults)!!, json("merge" to merge)).await() } - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { js.set(encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFields)).await() } + override suspend fun set( + strategy: SerializationStrategy, + data: T, + encodeDefaults: Boolean, + vararg mergeFields: String + ) = + rethrow { + js.set( + encode(strategy, data, encodeDefaults)!!, + json("mergeFields" to mergeFields) + ).await() + } - actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { js.set(encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())).await() } + override suspend fun set( + strategy: SerializationStrategy, + data: T, + encodeDefaults: Boolean, + vararg mergeFieldPaths: FieldPath + ) = + rethrow { + js.set( + encode(strategy, data, encodeDefaults)!!, + json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray()) + ).await() + } actual suspend inline fun update(data: T, encodeDefaults: Boolean) = rethrow { js.update(encode(data, encodeDefaults)!!).await() } - actual suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = + override suspend fun update( + strategy: SerializationStrategy, + data: T, + encodeDefaults: Boolean + ) = rethrow { js.update(encode(strategy, data, encodeDefaults)!!).await() } - actual suspend fun update(vararg fieldsAndValues: Pair) = rethrow { + override suspend fun update(vararg fieldsAndValues: Pair) = rethrow { fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } ?.map { (field, value) -> field to encode(value, true) } ?.let { encoded -> js.update(encoded.toMap()) } ?.await() }.run { Unit } - actual suspend fun update(vararg fieldsAndValues: Pair) = rethrow { - fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } - ?.map { (field, value) -> field.js to encode(value, true) } - ?.let { encoded -> - js.update( - encoded.first().first, - encoded.first().second, - *encoded.drop(1) - .flatMap { (field, value) -> listOf(field, value) } - .toTypedArray() - ) - } - ?.await() - }.run { Unit } + override suspend fun update(vararg fieldsAndValues: Pair) = rethrow { + fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } + ?.map { (field, value) -> field.js to encode(value, true) } + ?.let { encoded -> + js.update( + encoded.first().first, + encoded.first().second, + *encoded.drop(1) + .flatMap { (field, value) -> listOf(field, value) } + .toTypedArray() + ).await() + }.run { Unit } + } - actual suspend fun delete() = rethrow { js.delete().await() } + override suspend fun delete() = rethrow { js.delete().await() } - actual suspend fun get() = rethrow { DocumentSnapshot(js.get().await()) } + override suspend fun get() = rethrow { DocumentSnapshot(js.get().await()) } - actual val snapshots get() = callbackFlow { - val unsubscribe = js.onSnapshot( - { safeOffer(DocumentSnapshot(it)) }, - { close(errorToException(it)) } - ) - awaitClose { unsubscribe() } - } + override val snapshots: Flow + get() = callbackFlow { + val unsubscribe = js.onSnapshot( + { safeOffer(DocumentSnapshot(it)) }, + { close(errorToException(it)) } + ) + awaitClose { unsubscribe() } + } } actual open class Query(open val js: firebase.firestore.Query) { - actual suspend fun get() = rethrow { QuerySnapshot(js.get().await()) } + actual suspend fun get() = rethrow { QuerySnapshot(js.get().await()) } actual fun limit(limit: Number) = Query(js.limit(limit.toDouble())) - internal actual fun _where(field: String, equalTo: Any?) = rethrow { Query(js.where(field, "==", equalTo)) } - internal actual fun _where(path: FieldPath, equalTo: Any?) = rethrow { Query(js.where(path.js, "==", equalTo)) } + internal actual fun _where(field: String, equalTo: Any?) = + rethrow { Query(js.where(field, "==", equalTo)) } - internal actual fun _where(field: String, equalTo: DocumentReference) = rethrow { Query(js.where(field, "==", equalTo.js)) } - internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = rethrow { Query(js.where(path.js, "==", equalTo.js)) } + internal actual fun _where(path: FieldPath, equalTo: Any?) = + rethrow { Query(js.where(path.js, "==", equalTo)) } + + internal actual fun _where(field: String, equalTo: DocumentReference) = + rethrow { Query(js.where(field, "==", (equalTo as DocumentReferenceWrapper).js)) } + + internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = + rethrow { Query(js.where(path.js, "==", (equalTo as DocumentReferenceWrapper).js)) } internal actual fun _where( field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, @@ -285,8 +432,13 @@ actual open class Query(open val js: firebase.firestore.Query) { } internal actual fun _where( - path: FieldPath, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, - lessThanOrEqualTo: Any?, greaterThanOrEqualTo: Any? + path: FieldPath, + lessThan: Any?, + greaterThan: Any?, + arrayContains: Any?, + notEqualTo: Any?, + lessThanOrEqualTo: Any?, + greaterThanOrEqualTo: Any? ) = rethrow { Query( when { @@ -307,7 +459,11 @@ actual open class Query(open val js: firebase.firestore.Query) { Query( when { inArray != null -> js.where(field, "in", inArray.toTypedArray()) - arrayContainsAny != null -> js.where(field, "array-contains-any", arrayContainsAny.toTypedArray()) + arrayContainsAny != null -> js.where( + field, + "array-contains-any", + arrayContainsAny.toTypedArray() + ) notInArray != null -> js.where(field, "not-in", notInArray.toTypedArray()) else -> js } @@ -315,12 +471,19 @@ actual open class Query(open val js: firebase.firestore.Query) { } internal actual fun _where( - path: FieldPath, inArray: List?, arrayContainsAny: List?, notInArray: List? + path: FieldPath, + inArray: List?, + arrayContainsAny: List?, + notInArray: List? ) = rethrow { Query( when { inArray != null -> js.where(path.js, "in", inArray.toTypedArray()) - arrayContainsAny != null -> js.where(path.js, "array-contains-any", arrayContainsAny.toTypedArray()) + arrayContainsAny != null -> js.where( + path.js, + "array-contains-any", + arrayContainsAny.toTypedArray() + ) notInArray != null -> js.where(path.js, "not-in", notInArray.toTypedArray()) else -> js } @@ -335,39 +498,53 @@ actual open class Query(open val js: firebase.firestore.Query) { Query(js.orderBy(field.js, direction.jsString)) } - actual val snapshots get() = callbackFlow { - val unsubscribe = rethrow { - js.onSnapshot( - { safeOffer(QuerySnapshot(it)) }, - { close(errorToException(it)) } - ) + actual val snapshots + get() = callbackFlow { + val unsubscribe = rethrow { + js.onSnapshot( + { safeOffer(QuerySnapshot(it)) }, + { close(errorToException(it)) } + ) + } + awaitClose { rethrow { unsubscribe() } } } - awaitClose { rethrow { unsubscribe() } } - } } -actual class CollectionReference(override val js: firebase.firestore.CollectionReference) : Query(js) { +actual class CollectionReferenceWrapper(override val js: firebase.firestore.CollectionReference) : + Query(js), CollectionReference { + + override val path: String + get() = rethrow { js.path } - actual val path: String - get() = rethrow { js.path } + override fun document(documentPath: String) = + rethrow { DocumentReferenceWrapper(js.doc(documentPath)) } - actual fun document(documentPath: String) = rethrow { DocumentReference(js.doc(documentPath)) } + override fun document() = rethrow { DocumentReferenceWrapper(js.doc()) } - actual fun document() = rethrow { DocumentReference(js.doc()) } + actual suspend inline fun add(data: T, encodeDefaults: Boolean): DocumentReference = + rethrow { DocumentReferenceWrapper(js.add(encode(data, encodeDefaults)!!).await()) } - actual suspend inline fun add(data: T, encodeDefaults: Boolean) = - rethrow { DocumentReference(js.add(encode(data, encodeDefaults)!!).await()) } + override suspend fun add( + data: T, + strategy: SerializationStrategy, + encodeDefaults: Boolean + ) = + rethrow { DocumentReferenceWrapper(js.add(encode(strategy, data, encodeDefaults)!!).await()) } - actual suspend fun add(data: T, strategy: SerializationStrategy, encodeDefaults: Boolean) = - rethrow { DocumentReference(js.add(encode(strategy, data, encodeDefaults)!!).await()) } - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { DocumentReference(js.add(encode(strategy, data, encodeDefaults)!!).await()) } + override suspend fun add( + strategy: SerializationStrategy, + data: T, + encodeDefaults: Boolean + ) = + rethrow { DocumentReferenceWrapper(js.add(encode(strategy, data, encodeDefaults)!!).await()) } } -actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) +actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : + FirebaseException(code.toString(), cause) @Suppress("EXTENSION_SHADOWED_BY_MEMBER") -actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code +actual val FirebaseFirestoreException.code: FirestoreExceptionCode + get() = code actual class QuerySnapshot(val js: firebase.firestore.QuerySnapshot) { actual val documents @@ -391,7 +568,8 @@ actual class DocumentChange(val js: firebase.firestore.DocumentChange) { actual class DocumentSnapshot(val js: firebase.firestore.DocumentSnapshot) { actual val id get() = rethrow { js.id } - actual val reference get() = rethrow { DocumentReference(js.ref) } + actual val reference: DocumentReference + get() = rethrow { DocumentReferenceWrapper(js.ref) } actual inline fun data(): T = rethrow { decode(value = js.data()) } @@ -417,17 +595,27 @@ actual class SnapshotMetadata(val js: firebase.firestore.SnapshotMetadata) { actual class FieldPath private constructor(val js: firebase.firestore.FieldPath) { actual constructor(vararg fieldNames: String) : this(dev.gitlive.firebase.firestore.rethrow { - js("Reflect").construct(firebase.firestore.FieldPath, fieldNames).unsafeCast() + js("Reflect").construct(firebase.firestore.FieldPath, fieldNames) + .unsafeCast() }) + actual val documentId: FieldPath get() = FieldPath(firebase.firestore.FieldPath.documentId) } actual object FieldValue { @JsName("_serverTimestamp") - actual val delete: Any get() = rethrow { firebase.firestore.FieldValue.delete() } - actual fun arrayUnion(vararg elements: Any): Any = rethrow { firebase.firestore.FieldValue.arrayUnion(*elements) } - actual fun arrayRemove(vararg elements: Any): Any = rethrow { firebase.firestore.FieldValue.arrayRemove(*elements) } - actual fun serverTimestamp(): Any = rethrow { firebase.firestore.FieldValue.serverTimestamp() } + actual val delete: Any + get() = rethrow { firebase.firestore.FieldValue.delete() } + + actual fun arrayUnion(vararg elements: Any): Any = + rethrow { firebase.firestore.FieldValue.arrayUnion(*elements) } + + actual fun arrayRemove(vararg elements: Any): Any = + rethrow { firebase.firestore.FieldValue.arrayRemove(*elements) } + + actual fun serverTimestamp(): Any = + rethrow { firebase.firestore.FieldValue.serverTimestamp() } + @JsName("deprecatedDelete") actual fun delete(): Any = delete } @@ -460,53 +648,14 @@ actual enum class FirestoreExceptionCode { UNAUTHENTICATED } -actual enum class Direction(internal val jsString : String) { +actual enum class Direction(internal val jsString: String) { ASCENDING("asc"), DESCENDING("desc"); } -actual enum class ChangeType(internal val jsString : String) { +actual enum class ChangeType(internal val jsString: String) { ADDED("added"), MODIFIED("modified"), REMOVED("removed"); } -inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.firestore.rethrow { function() } - -inline fun rethrow(function: () -> R): R { - try { - return function() - } catch (e: Exception) { - throw e - } catch(e: dynamic) { - throw errorToException(e) - } -} - -fun errorToException(e: dynamic) = (e?.code ?: e?.message ?: "") - .toString() - .toLowerCase() - .let { - when { - "cancelled" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.CANCELLED) - "invalid-argument" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.INVALID_ARGUMENT) - "deadline-exceeded" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.DEADLINE_EXCEEDED) - "not-found" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.NOT_FOUND) - "already-exists" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.ALREADY_EXISTS) - "permission-denied" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.PERMISSION_DENIED) - "resource-exhausted" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.RESOURCE_EXHAUSTED) - "failed-precondition" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.FAILED_PRECONDITION) - "aborted" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.ABORTED) - "out-of-range" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.OUT_OF_RANGE) - "unimplemented" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.UNIMPLEMENTED) - "internal" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.INTERNAL) - "unavailable" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.UNAVAILABLE) - "data-loss" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.DATA_LOSS) - "unauthenticated" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.UNAUTHENTICATED) - "unknown" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.UNKNOWN) - else -> { - println("Unknown error code in ${JSON.stringify(e)}") - FirebaseFirestoreException(e, FirestoreExceptionCode.UNKNOWN) - } - } -}