From cce8b826568317b1e486bdae398d8ecedd4a3b79 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Sun, 21 Apr 2024 14:12:01 +0200 Subject: [PATCH] Moved Firestore internals to different package --- .../gitlive/firebase/firestore/firestore.kt | 405 +--------------- .../NativeCollectionReferenceWrapper.kt | 25 + .../internal/NativeDocumentReference.kt | 88 ++++ .../internal/NativeDocumentSnapshotWrapper.kt | 29 ++ .../NativeFirebaseFirestoreWrapper.kt | 101 ++++ .../firestore/internal/NativeQueryWrapper.kt | 138 ++++++ .../internal/NativeTransactionWrapper.kt | 46 ++ .../internal/NativeWriteBatchWrapper.kt | 47 ++ .../firebase/firestore/internal/SetOptions.kt | 8 + .../firebase/firestore/internal/Source.kt | 10 + .../firestore/internal/callbackExecutorMap.kt | 8 + .../firestore/DocumentReferenceSerializer.kt | 1 + .../dev/gitlive/firebase/firestore/Filter.kt | 2 + .../gitlive/firebase/firestore/firestore.kt | 141 +----- .../NativeCollectionReferenceWrapper.kt | 18 + .../internal/NativeDocumentReference.kt | 27 ++ .../internal/NativeDocumentSnapshotWrapper.kt | 24 + .../NativeFirebaseFirestoreWrapper.kt | 24 + .../firestore/internal/NativeQueryWrapper.kt | 36 ++ .../internal/NativeTransactionWrapper.kt | 19 + .../internal/NativeWriteBatchWrapper.kt | 17 + .../firebase/firestore/internal/SafeValue.kt | 14 + .../firebase/firestore/internal/SetOptions.kt | 13 + .../gitlive/firebase/firestore/firestore.kt | 331 +------------- .../NativeCollectionReferenceWrapper.kt | 23 + .../internal/NativeDocumentReference.kt | 85 ++++ .../internal/NativeDocumentSnapshotWrapper.kt | 42 ++ .../NativeFirebaseFirestoreWrapper.kt | 63 +++ .../firestore/internal/NativeQueryWrapper.kt | 86 ++++ .../internal/NativeTransactionWrapper.kt | 48 ++ .../internal/NativeWriteBatchWrapper.kt | 47 ++ .../firebase/firestore/internal/Source.kt | 10 + .../firebase/firestore/internal/throwError.kt | 23 + .../gitlive/firebase/firestore/firestore.kt | 432 +----------------- .../firebase/firestore/internal/JSQueryGet.kt | 7 + .../NativeCollectionReferenceWrapper.kt | 38 ++ .../internal/NativeDocumentReference.kt | 110 +++++ .../internal/NativeDocumentSnapshotWrapper.kt | 40 ++ .../NativeFirebaseFirestoreWrapper.kt | 113 +++++ .../firestore/internal/NativeQueryWrapper.kt | 154 +++++++ .../internal/NativeTransactionWrapper.kt | 57 +++ .../internal/NativeWriteBatchWrapper.kt | 53 +++ .../firebase/firestore/internal/SetOptions.kt | 12 + 43 files changed, 1720 insertions(+), 1295 deletions(-) create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt create mode 100644 firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt create mode 100644 firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt create mode 100644 firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/JSQueryGet.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt create mode 100644 firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index c1bfeedde..e190cd5a6 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -6,28 +6,11 @@ package dev.gitlive.firebase.firestore import com.google.android.gms.tasks.TaskExecutors -import com.google.firebase.firestore.MemoryCacheSettings -import com.google.firebase.firestore.MemoryEagerGcSettings -import com.google.firebase.firestore.MemoryLruGcSettings -import com.google.firebase.firestore.MetadataChanges -import dev.gitlive.firebase.internal.EncodedObject -import com.google.firebase.firestore.PersistentCacheSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import kotlinx.coroutines.channels.ProducerScope -import dev.gitlive.firebase.firestore.Source.* -import dev.gitlive.firebase.internal.android -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.tasks.await -import java.util.concurrent.ConcurrentHashMap +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper import java.util.concurrent.Executor -import com.google.firebase.firestore.FieldPath as AndroidFieldPath -import com.google.firebase.firestore.Filter as AndroidFilter import com.google.firebase.firestore.Query as AndroidQuery -import com.google.firebase.firestore.firestoreSettings as androidFirestoreSettings import com.google.firebase.firestore.memoryCacheSettings as androidMemoryCacheSettings import com.google.firebase.firestore.memoryEagerGcSettings as androidMemoryEagerGcSettings import com.google.firebase.firestore.memoryLruGcSettings as androidMemoryLruGcSettings @@ -55,79 +38,7 @@ val LocalCacheSettings.android: com.google.firebase.firestore.LocalCacheSettings } } -// Since on iOS Callback threads are set as settings, we store the settings explicitly here as well -private val callbackExecutorMap = ConcurrentHashMap() - actual typealias NativeFirebaseFirestore = com.google.firebase.firestore.FirebaseFirestore -internal actual class NativeFirebaseFirestoreWrapper actual constructor(actual val native: NativeFirebaseFirestore) { - - actual var settings: FirebaseFirestoreSettings - get() = with(native.firestoreSettings) { - FirebaseFirestoreSettings( - isSslEnabled, - host, - cacheSettings?.let { localCacheSettings -> - when (localCacheSettings) { - is MemoryCacheSettings -> { - val garbageCollectionSettings = when (val settings = localCacheSettings.garbageCollectorSettings) { - is MemoryEagerGcSettings -> MemoryGarbageCollectorSettings.Eager - is MemoryLruGcSettings -> MemoryGarbageCollectorSettings.LRUGC(settings.sizeBytes) - else -> throw IllegalArgumentException("Existing settings does not have valid GarbageCollectionSettings") - } - LocalCacheSettings.Memory(garbageCollectionSettings) - } - - is PersistentCacheSettings -> LocalCacheSettings.Persistent(localCacheSettings.sizeBytes) - else -> throw IllegalArgumentException("Existing settings is not of a valid type") - } - } ?: kotlin.run { - @Suppress("DEPRECATION") - when { - isPersistenceEnabled -> LocalCacheSettings.Persistent(cacheSizeBytes) - cacheSizeBytes == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED -> LocalCacheSettings.Memory(MemoryGarbageCollectorSettings.Eager) - else -> LocalCacheSettings.Memory(MemoryGarbageCollectorSettings.LRUGC(cacheSizeBytes)) - } - }, - callbackExecutorMap[native] ?: TaskExecutors.MAIN_THREAD - ) - } - set(value) { - native.firestoreSettings = androidFirestoreSettings { - isSslEnabled = value.sslEnabled - host = value.host - setLocalCacheSettings(value.cacheSettings.android) - } - callbackExecutorMap[native] = value.callbackExecutor - } - - actual fun collection(collectionPath: String) = native.collection(collectionPath) - - actual fun collectionGroup(collectionId: String) = native.collectionGroup(collectionId) - - actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - - actual fun batch() = native.batch() - - actual fun setLoggingEnabled(loggingEnabled: Boolean) = - com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) - - actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T = - native.runTransaction { runBlocking { it.func() } }.await() - - actual suspend fun clearPersistence() = - native.clearPersistence().await().run { } - - actual fun useEmulator(host: String, port: Int) { - native.useEmulator(host, port) - } - - actual suspend fun disableNetwork() = - native.disableNetwork().await().run { } - - actual suspend fun enableNetwork() = - native.enableNetwork().await().run { } - -} val FirebaseFirestore.android get() = native @@ -176,288 +87,21 @@ actual fun firestoreSettings( } }.apply(builder).build() -internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { - is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() - is SetOptions.Overwrite -> null - is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) - is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) -} - actual typealias NativeWriteBatch = com.google.firebase.firestore.WriteBatch -@PublishedApi -internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeWriteBatchWrapper = (setOptions.android?.let { - native.set(documentRef.android, encodedData.android, it) - } ?: native.set(documentRef.android, encodedData.android)).let { - this - } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - native.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - native.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - native.delete(documentRef.android).let { this } - - actual suspend fun commit() { - native.commit().await() - } -} - val WriteBatch.android get() = native actual typealias NativeTransaction = com.google.firebase.firestore.Transaction -@PublishedApi -internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeTransactionWrapper { - setOptions.android?.let { - native.set(documentRef.android, encodedData.android, it) - } ?: native.set(documentRef.android, encodedData.android) - return this - } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - native.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - native.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - native.delete(documentRef.android).let { this } - - actual suspend fun get(documentRef: DocumentReference) = - NativeDocumentSnapshotWrapper(native.get(documentRef.android)) -} - val Transaction.android get() = native /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = com.google.firebase.firestore.DocumentReference -@PublishedApi -internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { - val android: NativeDocumentReferenceType by ::nativeValue - actual val id: String - get() = android.id - - actual val path: String - get() = android.path - - actual val parent: NativeCollectionReferenceWrapper - get() = NativeCollectionReferenceWrapper(android.parent) - - actual fun collection(collectionPath: String) = android.collection(collectionPath) - - actual suspend fun get(source: Source) = - android.get(source.toAndroidSource()).await() - - actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) { - val task = (setOptions.android?.let { - android.set(encodedData.android, it) - } ?: android.set(encodedData.android)) - task.await() - } - - actual suspend fun updateEncoded(encodedData: EncodedObject) { - android.update(encodedData.android).await() - } - - actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { - android.update(encodedFieldsAndValues.toMap()) - }?.await() - } - - actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - android.update(field, value, *moreFieldsAndValues) - }?.await() - } - - actual suspend fun delete() { - android.delete().await() - } - - actual val snapshots: Flow get() = snapshots() - - actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception -> - snapshot?.let { trySend(snapshot) } - exception?.let { close(exception) } - } - - override fun equals(other: Any?): Boolean = - this === other || other is NativeDocumentReference && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - private fun addSnapshotListener( - includeMetadataChanges: Boolean = false, - listener: ProducerScope.(com.google.firebase.firestore.DocumentSnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit - ) = callbackFlow { - val executor = callbackExecutorMap[android.firestore] ?: TaskExecutors.MAIN_THREAD - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val registration = android.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> - listener(snapshots, exception) - } - awaitClose { registration.remove() } - } -} - val DocumentReference.android get() = native.android actual typealias NativeQuery = AndroidQuery -@PublishedApi -internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: AndroidQuery) { - - actual fun limit(limit: Number) = native.limit(limit.toLong()) - - actual val snapshots get() = callbackFlow { - val listener = native.addSnapshotListener { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val metadataChanges = - if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = native.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - actual suspend fun get(source: Source): QuerySnapshot = QuerySnapshot(native.get(source.toAndroidSource()).await()) - - actual fun where(filter: Filter) = native.where(filter.toAndroidFilter()) - - private fun Filter.toAndroidFilter(): AndroidFilter = when (this) { - is Filter.And -> AndroidFilter.and(*filters.map { it.toAndroidFilter() }.toTypedArray()) - is Filter.Or -> AndroidFilter.or(*filters.map { it.toAndroidFilter() }.toTypedArray()) - is Filter.Field -> { - when (constraint) { - is WhereConstraint.ForNullableObject -> { - val modifier: (String, Any?) -> AndroidFilter = when (constraint) { - is WhereConstraint.EqualTo -> AndroidFilter::equalTo - is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo - } - modifier.invoke(field, constraint.safeValue) - } - is WhereConstraint.ForObject -> { - val modifier: (String, Any) -> AndroidFilter = when (constraint) { - is WhereConstraint.LessThan -> AndroidFilter::lessThan - is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan - is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo - is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo - is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains - } - modifier.invoke(field, constraint.safeValue) - } - is WhereConstraint.ForArray -> { - val modifier: (String, List) -> AndroidFilter = when (constraint) { - is WhereConstraint.InArray -> AndroidFilter::inArray - is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny - is WhereConstraint.NotInArray -> AndroidFilter::notInArray - } - modifier.invoke(field, constraint.safeValues) - } - } - } - is Filter.Path -> { - when (constraint) { - is WhereConstraint.ForNullableObject -> { - val modifier: (AndroidFieldPath, Any?) -> AndroidFilter = when (constraint) { - is WhereConstraint.EqualTo -> AndroidFilter::equalTo - is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo - } - modifier.invoke(path.android, constraint.safeValue) - } - is WhereConstraint.ForObject -> { - val modifier: (AndroidFieldPath, Any) -> AndroidFilter = when (constraint) { - is WhereConstraint.LessThan -> AndroidFilter::lessThan - is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan - is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo - is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo - is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains - } - modifier.invoke(path.android, constraint.safeValue) - } - is WhereConstraint.ForArray -> { - val modifier: (AndroidFieldPath, List) -> AndroidFilter = when (constraint) { - is WhereConstraint.InArray -> AndroidFilter::inArray - is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny - is WhereConstraint.NotInArray -> AndroidFilter::notInArray - } - modifier.invoke(path.android, constraint.safeValues) - } - } - } - } - - actual fun orderBy(field: String, direction: Direction) = native.orderBy(field, direction) - actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.orderBy(field, direction) - - actual fun startAfter(document: NativeDocumentSnapshot) = native.startAfter(document) - actual fun startAfter(vararg fieldValues: Any) = native.startAfter(*fieldValues) - actual fun startAt(document: NativeDocumentSnapshot) = native.startAt(document) - actual fun startAt(vararg fieldValues: Any) = native.startAt(*fieldValues) - - actual fun endBefore(document: NativeDocumentSnapshot) = native.endBefore(document) - actual fun endBefore(vararg fieldValues: Any) = native.endBefore(*fieldValues) - actual fun endAt(document: NativeDocumentSnapshot) = native.endAt(document) - actual fun endAt(vararg fieldValues: Any) = native.endAt(*fieldValues) - - private fun addSnapshotListener( - includeMetadataChanges: Boolean = false, - listener: ProducerScope.(com.google.firebase.firestore.QuerySnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit - ) = callbackFlow { - val executor = callbackExecutorMap[native.firestore] ?: TaskExecutors.MAIN_THREAD - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val registration = native.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> - listener(snapshots, exception) - } - awaitClose { registration.remove() } - } -} - val Query.android get() = native actual typealias Direction = com.google.firebase.firestore.Query.Direction @@ -465,23 +109,6 @@ actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type actual typealias NativeCollectionReference = com.google.firebase.firestore.CollectionReference -@PublishedApi -internal actual class NativeCollectionReferenceWrapper internal actual constructor(override actual val native: NativeCollectionReference) : NativeQueryWrapper(native) { - - actual val path: String - get() = native.path - - actual val document: NativeDocumentReference - get() = NativeDocumentReference(native.document()) - - actual val parent: NativeDocumentReference? - get() = native.parent?.let{ NativeDocumentReference(it) } - - actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(native.add(data.android).await()) -} - val CollectionReference.android get() = native actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException @@ -511,30 +138,6 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC actual typealias NativeDocumentSnapshot = com.google.firebase.firestore.DocumentSnapshot -@PublishedApi -internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: com.google.firebase.firestore.DocumentSnapshot) { - - actual val id get() = native.id - actual val reference get() = NativeDocumentReference(native.reference) - - actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(field, serverTimestampBehavior.toAndroid()) - actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(fieldPath, serverTimestampBehavior.toAndroid()) - actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = native.getData(serverTimestampBehavior.toAndroid()) - - actual fun contains(field: String) = native.contains(field) - actual fun contains(fieldPath: EncodedFieldPath) = native.contains(fieldPath) - - actual val exists get() = native.exists() - - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) - - fun ServerTimestampBehavior.toAndroid(): com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior = when (this) { - ServerTimestampBehavior.ESTIMATE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.ESTIMATE - ServerTimestampBehavior.NONE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.NONE - ServerTimestampBehavior.PREVIOUS -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.PREVIOUS - } -} - val DocumentSnapshot.android get() = native actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { @@ -564,9 +167,3 @@ actual class FieldPath private constructor(val android: com.google.firebase.fire actual typealias EncodedFieldPath = com.google.firebase.firestore.FieldPath internal typealias NativeSource = com.google.firebase.firestore.Source - -private fun Source.toAndroidSource() = when(this) { - CACHE -> NativeSource.CACHE - SERVER -> NativeSource.SERVER - DEFAULT -> NativeSource.DEFAULT -} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..0d41e1e13 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,25 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android +import kotlinx.coroutines.tasks.await + +@PublishedApi +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + actual val path: String + get() = native.path + + actual val document: NativeDocumentReference + get() = NativeDocumentReference(native.document()) + + actual val parent: NativeDocumentReference? + get() = native.parent?.let{ NativeDocumentReference(it) } + + actual fun document(documentPath: String) = + NativeDocumentReference(native.document(documentPath)) + + actual suspend fun addEncoded(data: EncodedObject) = + NativeDocumentReference(native.add(data.android).await()) +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..b0553e921 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,88 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.android.gms.tasks.TaskExecutors +import com.google.firebase.firestore.MetadataChanges +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.tasks.await + +@PublishedApi +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + val android: NativeDocumentReferenceType by ::nativeValue + actual val id: String + get() = android.id + + actual val path: String + get() = android.path + + actual val parent: NativeCollectionReferenceWrapper + get() = NativeCollectionReferenceWrapper(android.parent) + + actual fun collection(collectionPath: String) = android.collection(collectionPath) + + actual suspend fun get(source: Source) = + android.get(source.toAndroidSource()).await() + + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) { + val task = (setOptions.android?.let { + android.set(encodedData.android, it) + } ?: android.set(encodedData.android)) + task.await() + } + + actual suspend fun updateEncoded(encodedData: EncodedObject) { + android.update(encodedData.android).await() + } + + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { + android.update(encodedFieldsAndValues.toMap()) + }?.await() + } + + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + android.update(field, value, *moreFieldsAndValues) + }?.await() + } + + actual suspend fun delete() { + android.delete().await() + } + + actual val snapshots: Flow get() = snapshots() + + actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception -> + snapshot?.let { trySend(snapshot) } + exception?.let { close(exception) } + } + + override fun equals(other: Any?): Boolean = + this === other || other is NativeDocumentReference && nativeValue == other.nativeValue + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() + + private fun addSnapshotListener( + includeMetadataChanges: Boolean = false, + listener: ProducerScope.(com.google.firebase.firestore.DocumentSnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit + ) = callbackFlow { + val executor = callbackExecutorMap[android.firestore] ?: TaskExecutors.MAIN_THREAD + val metadataChanges = + if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val registration = + android.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> + listener(snapshots, exception) + } + awaitClose { registration.remove() } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..70ec6128b --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,29 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata + +@PublishedApi +internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: com.google.firebase.firestore.DocumentSnapshot) { + + actual val id get() = native.id + actual val reference get() = NativeDocumentReference(native.reference) + + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(field, serverTimestampBehavior.toAndroid()) + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(fieldPath, serverTimestampBehavior.toAndroid()) + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = native.getData(serverTimestampBehavior.toAndroid()) + + actual fun contains(field: String) = native.contains(field) + actual fun contains(fieldPath: EncodedFieldPath) = native.contains(fieldPath) + + actual val exists get() = native.exists() + + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) + + fun ServerTimestampBehavior.toAndroid(): com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior = when (this) { + ServerTimestampBehavior.ESTIMATE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.ESTIMATE + ServerTimestampBehavior.NONE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.NONE + ServerTimestampBehavior.PREVIOUS -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.PREVIOUS + } +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..189666ff5 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,101 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.android.gms.tasks.TaskExecutors +import com.google.firebase.firestore.MemoryCacheSettings +import com.google.firebase.firestore.MemoryEagerGcSettings +import com.google.firebase.firestore.MemoryLruGcSettings +import com.google.firebase.firestore.PersistentCacheSettings +import com.google.firebase.firestore.firestoreSettings +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.LocalCacheSettings +import dev.gitlive.firebase.firestore.MemoryGarbageCollectorSettings +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.android +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.tasks.await + +internal actual class NativeFirebaseFirestoreWrapper actual constructor(actual val native: NativeFirebaseFirestore) { + + actual var settings: FirebaseFirestoreSettings + get() = with(native.firestoreSettings) { + FirebaseFirestoreSettings( + isSslEnabled, + host, + cacheSettings?.let { localCacheSettings -> + when (localCacheSettings) { + is MemoryCacheSettings -> { + val garbageCollectionSettings = + when (val settings = localCacheSettings.garbageCollectorSettings) { + is MemoryEagerGcSettings -> MemoryGarbageCollectorSettings.Eager + is MemoryLruGcSettings -> MemoryGarbageCollectorSettings.LRUGC( + settings.sizeBytes + ) + + else -> throw IllegalArgumentException("Existing settings does not have valid GarbageCollectionSettings") + } + LocalCacheSettings.Memory(garbageCollectionSettings) + } + + is PersistentCacheSettings -> LocalCacheSettings.Persistent( + localCacheSettings.sizeBytes + ) + + else -> throw IllegalArgumentException("Existing settings is not of a valid type") + } + } ?: kotlin.run { + @Suppress("DEPRECATION") + when { + isPersistenceEnabled -> LocalCacheSettings.Persistent(cacheSizeBytes) + cacheSizeBytes == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED -> LocalCacheSettings.Memory( + MemoryGarbageCollectorSettings.Eager + ) + + else -> LocalCacheSettings.Memory( + MemoryGarbageCollectorSettings.LRUGC( + cacheSizeBytes + ) + ) + } + }, + callbackExecutorMap[native] ?: TaskExecutors.MAIN_THREAD + ) + } + set(value) { + native.firestoreSettings = firestoreSettings { + isSslEnabled = value.sslEnabled + host = value.host + setLocalCacheSettings(value.cacheSettings.android) + } + callbackExecutorMap[native] = value.callbackExecutor + } + + actual fun collection(collectionPath: String) = native.collection(collectionPath) + + actual fun collectionGroup(collectionId: String) = native.collectionGroup(collectionId) + + actual fun document(documentPath: String) = + NativeDocumentReference(native.document(documentPath)) + + actual fun batch() = native.batch() + + actual fun setLoggingEnabled(loggingEnabled: Boolean) = + com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) + + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T = + native.runTransaction { runBlocking { it.func() } }.await() + + actual suspend fun clearPersistence() = + native.clearPersistence().await().run { } + + actual fun useEmulator(host: String, port: Int) { + native.useEmulator(host, port) + } + + actual suspend fun disableNetwork() = + native.disableNetwork().await().run { } + + actual suspend fun enableNetwork() = + native.enableNetwork().await().run { } + +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..349c1fd9f --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,138 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.android.gms.tasks.TaskExecutors +import com.google.firebase.firestore.FieldPath +import com.google.firebase.firestore.MetadataChanges +import com.google.firebase.firestore.Query +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.WhereConstraint +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.tasks.await + +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: Query) { + + actual fun limit(limit: Number) = native.limit(limit.toLong()) + + actual val snapshots get() = callbackFlow { + val listener = native.addSnapshotListener { snapshot, exception -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + exception?.let { close(exception) } + } + awaitClose { listener.remove() } + } + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val metadataChanges = + if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val listener = native.addSnapshotListener(metadataChanges) { snapshot, exception -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + exception?.let { close(exception) } + } + awaitClose { listener.remove() } + } + + actual suspend fun get(source: Source): QuerySnapshot = + QuerySnapshot(native.get(source.toAndroidSource()).await()) + + actual fun where(filter: Filter) = native.where(filter.toAndroidFilter()) + + private fun Filter.toAndroidFilter(): com.google.firebase.firestore.Filter = when (this) { + is Filter.And -> com.google.firebase.firestore.Filter.and(*filters.map { it.toAndroidFilter() } + .toTypedArray()) + is Filter.Or -> com.google.firebase.firestore.Filter.or(*filters.map { it.toAndroidFilter() } + .toTypedArray()) + is Filter.Field -> { + when (constraint) { + is WhereConstraint.ForNullableObject -> { + val modifier: (String, Any?) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.EqualTo -> com.google.firebase.firestore.Filter::equalTo + is WhereConstraint.NotEqualTo -> com.google.firebase.firestore.Filter::notEqualTo + } + modifier.invoke(field, constraint.safeValue) + } + is WhereConstraint.ForObject -> { + val modifier: (String, Any) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.LessThan -> com.google.firebase.firestore.Filter::lessThan + is WhereConstraint.GreaterThan -> com.google.firebase.firestore.Filter::greaterThan + is WhereConstraint.LessThanOrEqualTo -> com.google.firebase.firestore.Filter::lessThanOrEqualTo + is WhereConstraint.GreaterThanOrEqualTo -> com.google.firebase.firestore.Filter::greaterThanOrEqualTo + is WhereConstraint.ArrayContains -> com.google.firebase.firestore.Filter::arrayContains + } + modifier.invoke(field, constraint.safeValue) + } + is WhereConstraint.ForArray -> { + val modifier: (String, List) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.InArray -> com.google.firebase.firestore.Filter::inArray + is WhereConstraint.ArrayContainsAny -> com.google.firebase.firestore.Filter::arrayContainsAny + is WhereConstraint.NotInArray -> com.google.firebase.firestore.Filter::notInArray + } + modifier.invoke(field, constraint.safeValues) + } + } + } + is Filter.Path -> { + when (constraint) { + is WhereConstraint.ForNullableObject -> { + val modifier: (FieldPath, Any?) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.EqualTo -> com.google.firebase.firestore.Filter::equalTo + is WhereConstraint.NotEqualTo -> com.google.firebase.firestore.Filter::notEqualTo + } + modifier.invoke(path.android, constraint.safeValue) + } + is WhereConstraint.ForObject -> { + val modifier: (FieldPath, Any) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.LessThan -> com.google.firebase.firestore.Filter::lessThan + is WhereConstraint.GreaterThan -> com.google.firebase.firestore.Filter::greaterThan + is WhereConstraint.LessThanOrEqualTo -> com.google.firebase.firestore.Filter::lessThanOrEqualTo + is WhereConstraint.GreaterThanOrEqualTo -> com.google.firebase.firestore.Filter::greaterThanOrEqualTo + is WhereConstraint.ArrayContains -> com.google.firebase.firestore.Filter::arrayContains + } + modifier.invoke(path.android, constraint.safeValue) + } + is WhereConstraint.ForArray -> { + val modifier: (FieldPath, List) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.InArray -> com.google.firebase.firestore.Filter::inArray + is WhereConstraint.ArrayContainsAny -> com.google.firebase.firestore.Filter::arrayContainsAny + is WhereConstraint.NotInArray -> com.google.firebase.firestore.Filter::notInArray + } + modifier.invoke(path.android, constraint.safeValues) + } + } + } + } + + actual fun orderBy(field: String, direction: Direction) = native.orderBy(field, direction) + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.orderBy(field, direction) + + actual fun startAfter(document: NativeDocumentSnapshot) = native.startAfter(document) + actual fun startAfter(vararg fieldValues: Any) = native.startAfter(*fieldValues) + actual fun startAt(document: NativeDocumentSnapshot) = native.startAt(document) + actual fun startAt(vararg fieldValues: Any) = native.startAt(*fieldValues) + + actual fun endBefore(document: NativeDocumentSnapshot) = native.endBefore(document) + actual fun endBefore(vararg fieldValues: Any) = native.endBefore(*fieldValues) + actual fun endAt(document: NativeDocumentSnapshot) = native.endAt(document) + actual fun endAt(vararg fieldValues: Any) = native.endAt(*fieldValues) + + private fun addSnapshotListener( + includeMetadataChanges: Boolean = false, + listener: ProducerScope.(com.google.firebase.firestore.QuerySnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit + ) = callbackFlow { + val executor = callbackExecutorMap[native.firestore] ?: TaskExecutors.MAIN_THREAD + val metadataChanges = + if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val registration = + native.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> + listener(snapshots, exception) + } + awaitClose { registration.remove() } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..6db5b7ff3 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,46 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.android +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android + +@PublishedApi +internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeTransactionWrapper { + setOptions.android?.let { + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android) + return this + } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + native.delete(documentRef.android).let { this } + + actual suspend fun get(documentRef: DocumentReference) = + NativeDocumentSnapshotWrapper(native.get(documentRef.android)) +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..1c6c042b6 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,47 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.android +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android +import kotlinx.coroutines.tasks.await + +@PublishedApi +internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeWriteBatchWrapper = (setOptions.android?.let { + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android)).let { + this + } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + native.delete(documentRef.android).let { this } + + actual suspend fun commit() { + native.commit().await() + } +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt new file mode 100644 index 000000000..8b44d4799 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt @@ -0,0 +1,8 @@ +package dev.gitlive.firebase.firestore.internal + +internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { + is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() + is SetOptions.Overwrite -> null + is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) + is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt new file mode 100644 index 000000000..3b0d71950 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt @@ -0,0 +1,10 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeSource +import dev.gitlive.firebase.firestore.Source + +internal fun Source.toAndroidSource() = when(this) { + Source.CACHE -> NativeSource.CACHE + Source.SERVER -> NativeSource.SERVER + Source.DEFAULT -> NativeSource.DEFAULT +} \ No newline at end of file diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt new file mode 100644 index 000000000..a728f542c --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt @@ -0,0 +1,8 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.firebase.firestore.FirebaseFirestore +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executor + +// Since on iOS Callback threads are set as settings, we store the settings explicitly here as well +internal val callbackExecutorMap = ConcurrentHashMap() diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt index 79951b4a3..4a62715a4 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt @@ -1,5 +1,6 @@ package dev.gitlive.firebase.firestore +import dev.gitlive.firebase.firestore.internal.NativeDocumentReference import dev.gitlive.firebase.internal.FirebaseEncoder import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt index e6897c6d7..6be86251c 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt @@ -1,5 +1,7 @@ package dev.gitlive.firebase.firestore +import dev.gitlive.firebase.firestore.internal.safeValue + sealed interface WhereConstraint { sealed interface ForNullableObject : WhereConstraint { diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index b6cc94999..15a9528b5 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -10,6 +10,15 @@ import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.firestore.internal.NativeCollectionReferenceWrapper +import dev.gitlive.firebase.firestore.internal.NativeDocumentReference +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper +import dev.gitlive.firebase.firestore.internal.NativeFirebaseFirestoreWrapper +import dev.gitlive.firebase.firestore.internal.NativeQueryWrapper +import dev.gitlive.firebase.firestore.internal.NativeTransactionWrapper +import dev.gitlive.firebase.firestore.internal.NativeWriteBatchWrapper +import dev.gitlive.firebase.firestore.internal.SetOptions +import dev.gitlive.firebase.firestore.internal.safeValue import dev.gitlive.firebase.internal.decode import dev.gitlive.firebase.internal.encodeAsObject import kotlinx.coroutines.flow.Flow @@ -27,22 +36,6 @@ expect fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore expect class NativeFirebaseFirestore -internal expect class NativeFirebaseFirestoreWrapper internal constructor(native: NativeFirebaseFirestore) { - val native: NativeFirebaseFirestore - var settings: FirebaseFirestoreSettings - - fun collection(collectionPath: String): NativeCollectionReference - fun collectionGroup(collectionId: String): NativeQuery - fun document(documentPath: String): NativeDocumentReference - fun batch(): NativeWriteBatch - fun setLoggingEnabled(loggingEnabled: Boolean) - suspend fun clearPersistence() - suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T - fun useEmulator(host: String, port: Int) - suspend fun disableNetwork() - suspend fun enableNetwork() -} - class FirebaseFirestore internal constructor(private val wrapper: NativeFirebaseFirestoreWrapper) { constructor(native: NativeFirebaseFirestore) : this(NativeFirebaseFirestoreWrapper(native)) @@ -117,29 +110,7 @@ expect class FirebaseFirestoreSettings { expect fun firestoreSettings(settings: FirebaseFirestoreSettings? = null, builder: FirebaseFirestoreSettings.Builder.() -> Unit): FirebaseFirestoreSettings -@PublishedApi -internal sealed class SetOptions { - data object Merge : SetOptions() - data object Overwrite : SetOptions() - data class MergeFields(val fields: List) : SetOptions() - data class MergeFieldPaths(val fieldPaths: List) : SetOptions() { - val encodedFieldPaths = fieldPaths.map { it.encoded } - } -} - expect class NativeTransaction -@PublishedApi -internal expect class NativeTransactionWrapper internal constructor(native: NativeTransaction) { - - val native: NativeTransaction - - fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeTransactionWrapper - fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper - fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper - fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper - fun delete(documentRef: DocumentReference): NativeTransactionWrapper - suspend fun get(documentRef: DocumentReference): NativeDocumentSnapshotWrapper -} data class Transaction internal constructor(@PublishedApi internal val nativeWrapper: NativeTransactionWrapper) { @@ -219,32 +190,6 @@ data class Transaction internal constructor(@PublishedApi internal val nativeWra expect open class NativeQuery -@PublishedApi -internal expect open class NativeQueryWrapper internal constructor(native: NativeQuery) { - - open val native: NativeQuery - - fun limit(limit: Number): NativeQuery - val snapshots: Flow - fun snapshots(includeMetadataChanges: Boolean = false): Flow - suspend fun get(source: Source = Source.DEFAULT): QuerySnapshot - - fun where(filter: Filter): NativeQuery - - fun orderBy(field: String, direction: Direction): NativeQuery - fun orderBy(field: EncodedFieldPath, direction: Direction): NativeQuery - - fun startAfter(document: NativeDocumentSnapshot): NativeQuery - fun startAfter(vararg fieldValues: Any): NativeQuery - fun startAt(document: NativeDocumentSnapshot): NativeQuery - fun startAt(vararg fieldValues: Any): NativeQuery - - fun endBefore(document: NativeDocumentSnapshot): NativeQuery - fun endBefore(vararg fieldValues: Any): NativeQuery - fun endAt(document: NativeDocumentSnapshot): NativeQuery - fun endAt(vararg fieldValues: Any): NativeQuery -} - open class Query internal constructor(internal val nativeQuery: NativeQueryWrapper) { constructor(native: NativeQuery) : this(NativeQueryWrapper(native)) @@ -324,28 +269,8 @@ fun Query.where(path: FieldPath, inArray: List? = null, arrayContainsAny: L ) } -internal val Any.safeValue: Any get() = when (this) { - is Timestamp -> nativeValue - is GeoPoint -> nativeValue - is DocumentReference -> native.nativeValue - is Map<*, *> -> this.mapNotNull { (key, value) -> key?.let { it.safeValue to value?.safeValue } } - is Collection<*> -> this.mapNotNull { it?.safeValue } - else -> this -} - expect class NativeWriteBatch -@PublishedApi -internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch) { - val native: NativeWriteBatch - fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeWriteBatchWrapper - fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper - fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper - fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper - fun delete(documentRef: DocumentReference): NativeWriteBatchWrapper - suspend fun commit() -} - data class WriteBatch internal constructor(@PublishedApi internal val nativeWrapper: NativeWriteBatchWrapper) { constructor(native: NativeWriteBatch) : this(NativeWriteBatchWrapper(native)) @@ -432,24 +357,6 @@ data class WriteBatch internal constructor(@PublishedApi internal val nativeWrap /** A class representing a platform specific Firebase DocumentReference. */ expect class NativeDocumentReferenceType -@PublishedApi -internal expect class NativeDocumentReference(nativeValue: NativeDocumentReferenceType) { - val nativeValue: NativeDocumentReferenceType - val id: String - val path: String - val snapshots: Flow - val parent: NativeCollectionReferenceWrapper - fun snapshots(includeMetadataChanges: Boolean = false): Flow - - fun collection(collectionPath: String): NativeCollectionReference - suspend fun get(source: Source = Source.DEFAULT): NativeDocumentSnapshot - suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) - suspend fun updateEncoded(encodedData: EncodedObject) - suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) - suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) - suspend fun delete() -} - /** A class representing a Firebase DocumentReference. */ @Serializable(with = DocumentReferenceSerializer::class) data class DocumentReference internal constructor(@PublishedApi internal val native: NativeDocumentReference) { @@ -532,19 +439,6 @@ data class DocumentReference internal constructor(@PublishedApi internal val nat expect class NativeCollectionReference : NativeQuery -@PublishedApi -internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : NativeQueryWrapper { - - override val native: NativeCollectionReference - - val path: String - val document: NativeDocumentReference - val parent: NativeDocumentReference? - - fun document(documentPath: String): NativeDocumentReference - suspend fun addEncoded(data: EncodedObject): NativeDocumentReference -} - data class CollectionReference internal constructor(@PublishedApi internal val nativeWrapper: NativeCollectionReferenceWrapper) : Query(nativeWrapper) { constructor(native: NativeCollectionReference) : this(NativeCollectionReferenceWrapper(native)) @@ -627,23 +521,6 @@ expect class DocumentChange { } expect class NativeDocumentSnapshot -@PublishedApi -internal expect class NativeDocumentSnapshotWrapper internal constructor(native: NativeDocumentSnapshot) { - - val native: NativeDocumentSnapshot - - val exists: Boolean - val id: String - val reference: NativeDocumentReference - val metadata: SnapshotMetadata - - fun contains(field: String): Boolean - fun contains(fieldPath: EncodedFieldPath): Boolean - - fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? -} data class DocumentSnapshot internal constructor(@PublishedApi internal val nativeWrapper: NativeDocumentSnapshotWrapper) { diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..f4b38be4f --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,18 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.internal.EncodedObject + +@PublishedApi +internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : + NativeQueryWrapper { + + override val native: NativeCollectionReference + + val path: String + val document: NativeDocumentReference + val parent: NativeDocumentReference? + + fun document(documentPath: String): NativeDocumentReference + suspend fun addEncoded(data: EncodedObject): NativeDocumentReference +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..b50248ae9 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,27 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.internal.EncodedObject +import kotlinx.coroutines.flow.Flow + +@PublishedApi +internal expect class NativeDocumentReference(nativeValue: NativeDocumentReferenceType) { + val nativeValue: NativeDocumentReferenceType + val id: String + val path: String + val snapshots: Flow + val parent: NativeCollectionReferenceWrapper + fun snapshots(includeMetadataChanges: Boolean = false): Flow + + fun collection(collectionPath: String): NativeCollectionReference + suspend fun get(source: Source = Source.DEFAULT): NativeDocumentSnapshot + suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) + suspend fun updateEncoded(encodedData: EncodedObject) + suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) + suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) + suspend fun delete() +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..5db75d0e2 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,24 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata + +@PublishedApi +internal expect class NativeDocumentSnapshotWrapper internal constructor(native: NativeDocumentSnapshot) { + + val native: NativeDocumentSnapshot + + val exists: Boolean + val id: String + val reference: NativeDocumentReference + val metadata: SnapshotMetadata + + fun contains(field: String): Boolean + fun contains(fieldPath: EncodedFieldPath): Boolean + + fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..dc6957177 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,24 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.NativeWriteBatch + +internal expect class NativeFirebaseFirestoreWrapper internal constructor(native: NativeFirebaseFirestore) { + val native: NativeFirebaseFirestore + var settings: FirebaseFirestoreSettings + + fun collection(collectionPath: String): NativeCollectionReference + fun collectionGroup(collectionId: String): NativeQuery + fun document(documentPath: String): NativeDocumentReference + fun batch(): NativeWriteBatch + fun setLoggingEnabled(loggingEnabled: Boolean) + suspend fun clearPersistence() + suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T + fun useEmulator(host: String, port: Int) + suspend fun disableNetwork() + suspend fun enableNetwork() +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..8a2d8228b --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,36 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import kotlinx.coroutines.flow.Flow + +@PublishedApi +internal expect open class NativeQueryWrapper internal constructor(native: NativeQuery) { + + open val native: NativeQuery + + fun limit(limit: Number): NativeQuery + val snapshots: Flow + fun snapshots(includeMetadataChanges: Boolean = false): Flow + suspend fun get(source: Source = Source.DEFAULT): QuerySnapshot + + fun where(filter: Filter): NativeQuery + + fun orderBy(field: String, direction: Direction): NativeQuery + fun orderBy(field: EncodedFieldPath, direction: Direction): NativeQuery + + fun startAfter(document: NativeDocumentSnapshot): NativeQuery + fun startAfter(vararg fieldValues: Any): NativeQuery + fun startAt(document: NativeDocumentSnapshot): NativeQuery + fun startAt(vararg fieldValues: Any): NativeQuery + + fun endBefore(document: NativeDocumentSnapshot): NativeQuery + fun endBefore(vararg fieldValues: Any): NativeQuery + fun endAt(document: NativeDocumentSnapshot): NativeQuery + fun endAt(vararg fieldValues: Any): NativeQuery +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..abc7776a2 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,19 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.internal.EncodedObject + +@PublishedApi +internal expect class NativeTransactionWrapper internal constructor(native: NativeTransaction) { + + val native: NativeTransaction + + fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeTransactionWrapper + fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper + fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper + fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper + fun delete(documentRef: DocumentReference): NativeTransactionWrapper + suspend fun get(documentRef: DocumentReference): NativeDocumentSnapshotWrapper +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..60a2e9564 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,17 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.internal.EncodedObject + +@PublishedApi +internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch) { + val native: NativeWriteBatch + fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeWriteBatchWrapper + fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper + fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper + fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper + fun delete(documentRef: DocumentReference): NativeWriteBatchWrapper + suspend fun commit() +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt new file mode 100644 index 000000000..f424b244d --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt @@ -0,0 +1,14 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.GeoPoint +import dev.gitlive.firebase.firestore.Timestamp + +internal val Any.safeValue: Any get() = when (this) { + is Timestamp -> nativeValue + is GeoPoint -> nativeValue + is DocumentReference -> native.nativeValue + is Map<*, *> -> this.mapNotNull { (key, value) -> key?.let { it.safeValue to value?.safeValue } } + is Collection<*> -> this.mapNotNull { it?.safeValue } + else -> this +} \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt new file mode 100644 index 000000000..84fcbe604 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt @@ -0,0 +1,13 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.FieldPath + +@PublishedApi +internal sealed class SetOptions { + data object Merge : SetOptions() + data object Overwrite : SetOptions() + data class MergeFields(val fields: List) : SetOptions() + data class MergeFieldPaths(val fieldPaths: List) : SetOptions() { + val encodedFieldPaths = fieldPaths.map { it.encoded } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index c9b9867bc..3f81e6cd2 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -9,15 +9,10 @@ import cocoapods.FirebaseFirestoreInternal.FIRDocumentChangeType.* import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -import dev.gitlive.firebase.internal.EncodedObject -import dev.gitlive.firebase.internal.ios +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper import kotlinx.cinterop.* import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.runBlocking import platform.Foundation.NSError -import platform.Foundation.NSNull import platform.Foundation.NSNumber import platform.Foundation.numberWithLong import platform.darwin.dispatch_get_main_queue @@ -42,52 +37,6 @@ val LocalCacheSettings.ios: FIRLocalCacheSettingsProtocol get() = when (this) { actual typealias NativeFirebaseFirestore = FIRFirestore -@Suppress("UNCHECKED_CAST") -internal actual class NativeFirebaseFirestoreWrapper internal actual constructor(actual val native: NativeFirebaseFirestore) { - - actual var settings: FirebaseFirestoreSettings = firestoreSettings { }.also { - native.settings = it.ios - } - set(value) { - field = value - native.settings = value.ios - } - - actual fun collection(collectionPath: String) = native.collectionWithPath(collectionPath) - - actual fun collectionGroup(collectionId: String) = native.collectionGroupWithID(collectionId) - - actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - - actual fun batch() = native.batch() - - actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = - FIRFirestore.enableLogging(loggingEnabled) - - actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = - awaitResult { native.runTransactionWithBlock({ transaction, _ -> runBlocking { transaction!!.func() } }, it) } as T - - actual suspend fun clearPersistence() = - await { native.clearPersistenceWithCompletion(it) } - - actual fun useEmulator(host: String, port: Int) { - native.useEmulatorWithHost(host, port.toLong()) - settings = firestoreSettings(settings) { - this.host = "$host:$port" - cacheSettings = memoryCacheSettings { } - sslEnabled = false - } - } - - actual suspend fun disableNetwork() { - await { native.disableNetworkWithCompletion(it) } - } - - actual suspend fun enableNetwork() { - await { native.enableNetworkWithCompletion(it) } - } -} - val FirebaseFirestore.ios get() = native actual data class FirebaseFirestoreSettings( @@ -150,249 +99,23 @@ actual fun firestoreSettings( actual typealias NativeWriteBatch = FIRWriteBatch -@PublishedApi -internal actual class NativeWriteBatchWrapper actual constructor(actual val native: NativeWriteBatch) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeWriteBatchWrapper = when (setOptions) { - is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) - is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) - is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) - }.let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatchWrapper = native.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatchWrapper = native.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun delete(documentRef: DocumentReference) = - native.deleteDocument(documentRef.ios).let { this } - - actual suspend fun commit() = await { native.commitWithCompletion(it) } -} - val WriteBatch.ios get() = native actual typealias NativeTransaction = FIRTransaction -@PublishedApi -internal actual class NativeTransactionWrapper actual constructor(actual val native: FIRTransaction) { - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeTransactionWrapper = when (setOptions) { - is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) - is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) - is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) - }.let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransactionWrapper = native.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransactionWrapper = native.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun delete(documentRef: DocumentReference) = - native.deleteDocument(documentRef.ios).let { this } - - actual suspend fun get(documentRef: DocumentReference) = - throwError { NativeDocumentSnapshotWrapper(native.getDocument(documentRef.ios, it)!!) } - -} - val Transaction.ios get() = native /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = FIRDocumentReference -@PublishedApi -internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> - snapshot?.let { trySend(snapshot) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - val ios: NativeDocumentReferenceType by ::nativeValue - - actual val id: String - get() = ios.documentID - - actual val path: String - get() = ios.path - - actual val parent: NativeCollectionReferenceWrapper - get() = NativeCollectionReferenceWrapper(ios.parent) - - - actual fun collection(collectionPath: String) = ios.collectionWithPath(collectionPath) - - actual suspend fun get(source: Source) = - awaitResult { ios.getDocumentWithSource(source.toIosSource(), it) } - - actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = await { - when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData.ios, true, it) - is SetOptions.Overwrite -> ios.setData(encodedData.ios, false, it) - is SetOptions.MergeFields -> ios.setData(encodedData.ios, setOptions.fields, it) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData.ios, setOptions.encodedFieldPaths, it) - } - } - - actual suspend fun updateEncoded(encodedData: EncodedObject) = await { - ios.updateData(encodedData.ios, it) - } - - actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = await { - ios.updateData(encodedFieldsAndValues.toMap(), it) - } - - actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) = await { - ios.updateData(encodedFieldsAndValues.toMap(), it) - } - - actual suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } - - actual val snapshots get() = callbackFlow { - val listener = ios.addSnapshotListener { snapshot, error -> - snapshot?.let { trySend(snapshot) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - override fun equals(other: Any?): Boolean = - this === other || other is NativeDocumentReference && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() -} - val DocumentReference.ios get() = native.ios actual typealias NativeQuery = FIRQuery -@PublishedApi -internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { - - actual fun limit(limit: Number) = native.queryLimitedTo(limit.toLong()) - - actual suspend fun get(source: Source) = QuerySnapshot(awaitResult { native.getDocumentsWithSource(source.toIosSource(),it) }) - - actual val snapshots get() = callbackFlow { - val listener = native.addSnapshotListener { snapshot, error -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = native.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - actual fun where(filter: Filter) = native.queryWhereFilter(filter.toFIRFilter()) - - private fun Filter.toFIRFilter(): FIRFilter = when (this) { - is Filter.And -> FIRFilter.andFilterWithFilters(filters.map { it.toFIRFilter() }) - is Filter.Or -> FIRFilter.orFilterWithFilters(filters.map { it.toFIRFilter() }) - is Filter.Field -> when (constraint) { - is WhereConstraint.EqualTo -> FIRFilter.filterWhereField(field, isEqualTo = constraint.safeValue ?: NSNull.`null`()) - is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereField(field, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) - is WhereConstraint.LessThan -> FIRFilter.filterWhereField(field, isLessThan = constraint.safeValue) - is WhereConstraint.GreaterThan -> FIRFilter.filterWhereField(field, isGreaterThan = constraint.safeValue) - is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereField(field, isLessThanOrEqualTo = constraint.safeValue) - is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereField(field, isGreaterThanOrEqualTo = constraint.safeValue) - is WhereConstraint.ArrayContains -> FIRFilter.filterWhereField(field, arrayContains = constraint.safeValue) - is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereField(field, arrayContainsAny = constraint.safeValues) - is WhereConstraint.InArray -> FIRFilter.filterWhereField(field, `in` = constraint.safeValues) - is WhereConstraint.NotInArray -> FIRFilter.filterWhereField(field, notIn = constraint.safeValues) - } - is Filter.Path -> when (constraint) { - is WhereConstraint.EqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isEqualTo = constraint.safeValue ?: NSNull.`null`()) - is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) - is WhereConstraint.LessThan -> FIRFilter.filterWhereFieldPath(path.ios, isLessThan = constraint.safeValue) - is WhereConstraint.GreaterThan -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThan = constraint.safeValue) - is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isLessThanOrEqualTo = constraint.safeValue) - is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThanOrEqualTo = constraint.safeValue) - is WhereConstraint.ArrayContains -> FIRFilter.filterWhereFieldPath(path.ios, arrayContains = constraint.safeValue) - is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereFieldPath(path.ios, arrayContainsAny = constraint.safeValues) - is WhereConstraint.InArray -> FIRFilter.filterWhereFieldPath(path.ios, `in` = constraint.safeValues) - is WhereConstraint.NotInArray -> FIRFilter.filterWhereFieldPath(path.ios, notIn = constraint.safeValues) - } - } - - actual fun orderBy(field: String, direction: Direction) = native.queryOrderedByField(field, direction == Direction.DESCENDING) - actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.queryOrderedByFieldPath(field, direction == Direction.DESCENDING) - - actual fun startAfter(document: NativeDocumentSnapshot) = native.queryStartingAfterDocument(document) - actual fun startAfter(vararg fieldValues: Any) = native.queryStartingAfterValues(fieldValues.asList()) - actual fun startAt(document: NativeDocumentSnapshot) = native.queryStartingAtDocument(document) - actual fun startAt(vararg fieldValues: Any) = native.queryStartingAtValues(fieldValues.asList()) - - actual fun endBefore(document: NativeDocumentSnapshot) = native.queryEndingBeforeDocument(document) - actual fun endBefore(vararg fieldValues: Any) = native.queryEndingBeforeValues(fieldValues.asList()) - actual fun endAt(document: NativeDocumentSnapshot) = native.queryEndingAtDocument(document) - actual fun endAt(vararg fieldValues: Any) = native.queryEndingAtValues(fieldValues.asList()) -} - val Query.ios get() = native actual typealias NativeCollectionReference = FIRCollectionReference -@PublishedApi -internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { - - actual val path: String - get() = native.path - - actual val document get() = NativeDocumentReference(native.documentWithAutoID()) - - actual val parent get() = native.parent?.let{ NativeDocumentReference(it) } - - actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { native.addDocumentWithData(data.ios, it) }) -} - val CollectionReference.ios get() = native actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) @@ -475,40 +198,6 @@ actual class DocumentChange(val ios: FIRDocumentChange) { actual typealias NativeDocumentSnapshot = FIRDocumentSnapshot -@PublishedApi -internal actual class NativeDocumentSnapshotWrapper actual constructor(actual val native: NativeDocumentSnapshot) { - - actual val id get() = native.documentID - - actual val reference get() = NativeDocumentReference(native.reference) - - actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = - native.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - - // Despite its name implying otherwise, valueForField accepts both a String representation of a Field and a FIRFieldPath - actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = - native.valueForField(fieldPath, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - - actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = - native.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) - ?.mapValues { (_, value) -> - value?.takeIf { it !is NSNull } - } - - actual fun contains(field: String) = native.valueForField(field) != null - actual fun contains(fieldPath: EncodedFieldPath) = native.valueForField(fieldPath) != null - - actual val exists get() = native.exists - - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) - - fun ServerTimestampBehavior.toIos() : FIRServerTimestampBehavior = when (this) { - ServerTimestampBehavior.ESTIMATE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorEstimate - ServerTimestampBehavior.NONE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorNone - ServerTimestampBehavior.PREVIOUS -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorPrevious - } -} - val DocumentSnapshot.ios get() = native actual class SnapshotMetadata(val ios: FIRSnapshotMetadata) { @@ -530,18 +219,6 @@ actual class FieldPath private constructor(val ios: FIRFieldPath) { actual typealias EncodedFieldPath = FIRFieldPath -private fun T.throwError(block: T.(errorPointer: CPointer>) -> R): R { - memScoped { - val errorPointer: CPointer> = alloc>().ptr - val result = block(errorPointer) - val error: NSError? = errorPointer.pointed.value - if (error != null) { - throw error.toException() - } - return result - } -} - suspend inline fun awaitResult(function: (callback: (T?, NSError?) -> Unit) -> Unit): T { val job = CompletableDeferred() function { result, error -> @@ -566,9 +243,3 @@ suspend inline fun await(function: (callback: (NSError?) -> Unit) -> T): T { job.await() return result } - -private fun Source.toIosSource() = when (this) { - Source.CACHE -> FIRFirestoreSource.FIRFirestoreSourceCache - Source.SERVER -> FIRFirestoreSource.FIRFirestoreSourceServer - Source.DEFAULT -> FIRFirestoreSource.FIRFirestoreSourceDefault -} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..339a88385 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,23 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios + +@PublishedApi +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + actual val path: String + get() = native.path + + actual val document get() = NativeDocumentReference(native.documentWithAutoID()) + + actual val parent get() = native.parent?.let{ NativeDocumentReference(it) } + + actual fun document(documentPath: String) = + NativeDocumentReference(native.documentWithPath(documentPath)) + + actual suspend fun addEncoded(data: EncodedObject) = + NativeDocumentReference(await { native.addDocumentWithData(data.ios, it) }) +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..cd6f8fa31 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,85 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.firestore.awaitResult +import dev.gitlive.firebase.firestore.toException +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow + +@PublishedApi +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val listener = + ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> + snapshot?.let { trySend(snapshot) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + val ios: NativeDocumentReferenceType by ::nativeValue + + actual val id: String + get() = ios.documentID + + actual val path: String + get() = ios.path + + actual val parent: NativeCollectionReferenceWrapper + get() = NativeCollectionReferenceWrapper(ios.parent) + + + actual fun collection(collectionPath: String) = ios.collectionWithPath(collectionPath) + + actual suspend fun get(source: Source) = + awaitResult { ios.getDocumentWithSource(source.toIosSource(), it) } + + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = await { + when (setOptions) { + is SetOptions.Merge -> ios.setData(encodedData.ios, true, it) + is SetOptions.Overwrite -> ios.setData(encodedData.ios, false, it) + is SetOptions.MergeFields -> ios.setData(encodedData.ios, setOptions.fields, it) + is SetOptions.MergeFieldPaths -> ios.setData( + encodedData.ios, + setOptions.encodedFieldPaths, + it + ) + } + } + + actual suspend fun updateEncoded(encodedData: EncodedObject) = await { + ios.updateData(encodedData.ios, it) + } + + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = + await { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } + + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) = + await { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } + + actual suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } + + actual val snapshots get() = callbackFlow { + val listener = ios.addSnapshotListener { snapshot, error -> + snapshot?.let { trySend(snapshot) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + override fun equals(other: Any?): Boolean = + this === other || other is NativeDocumentReference && nativeValue == other.nativeValue + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..929a906ce --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,42 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRServerTimestampBehavior +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata +import platform.Foundation.NSNull + +@PublishedApi +internal actual class NativeDocumentSnapshotWrapper actual constructor(actual val native: NativeDocumentSnapshot) { + + actual val id get() = native.documentID + + actual val reference get() = NativeDocumentReference(native.reference) + + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + + // Despite its name implying otherwise, valueForField accepts both a String representation of a Field and a FIRFieldPath + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.valueForField(fieldPath, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) + ?.mapValues { (_, value) -> + value?.takeIf { it !is NSNull } + } + + actual fun contains(field: String) = native.valueForField(field) != null + actual fun contains(fieldPath: EncodedFieldPath) = native.valueForField(fieldPath) != null + + actual val exists get() = native.exists + + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) + + fun ServerTimestampBehavior.toIos() : FIRServerTimestampBehavior = when (this) { + ServerTimestampBehavior.ESTIMATE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorEstimate + ServerTimestampBehavior.NONE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorNone + ServerTimestampBehavior.PREVIOUS -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorPrevious + } +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..e95be1050 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,63 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRFirestore +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.firestore.awaitResult +import dev.gitlive.firebase.firestore.firestoreSettings +import dev.gitlive.firebase.firestore.memoryCacheSettings +import kotlinx.coroutines.runBlocking + +@Suppress("UNCHECKED_CAST") +internal actual class NativeFirebaseFirestoreWrapper internal actual constructor(actual val native: NativeFirebaseFirestore) { + + actual var settings: FirebaseFirestoreSettings = firestoreSettings { }.also { + native.settings = it.ios + } + set(value) { + field = value + native.settings = value.ios + } + + actual fun collection(collectionPath: String) = native.collectionWithPath(collectionPath) + + actual fun collectionGroup(collectionId: String) = native.collectionGroupWithID(collectionId) + + actual fun document(documentPath: String) = + NativeDocumentReference(native.documentWithPath(documentPath)) + + actual fun batch() = native.batch() + + actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = + FIRFirestore.enableLogging(loggingEnabled) + + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = + awaitResult { + native.runTransactionWithBlock( + { transaction, _ -> runBlocking { transaction!!.func() } }, + it + ) + } as T + + actual suspend fun clearPersistence() = + await { native.clearPersistenceWithCompletion(it) } + + actual fun useEmulator(host: String, port: Int) { + native.useEmulatorWithHost(host, port.toLong()) + settings = firestoreSettings(settings) { + this.host = "$host:$port" + cacheSettings = memoryCacheSettings { } + sslEnabled = false + } + } + + actual suspend fun disableNetwork() { + await { native.disableNetworkWithCompletion(it) } + } + + actual suspend fun enableNetwork() { + await { native.enableNetworkWithCompletion(it) } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..4a050998e --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,86 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRFilter +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.WhereConstraint +import dev.gitlive.firebase.firestore.awaitResult +import dev.gitlive.firebase.firestore.toException +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import platform.Foundation.NSNull + +@PublishedApi +internal actual open class NativeQueryWrapper internal actual constructor(actual open val native: NativeQuery) { + + actual fun limit(limit: Number) = native.queryLimitedTo(limit.toLong()) + + actual suspend fun get(source: Source) = + QuerySnapshot(awaitResult { native.getDocumentsWithSource(source.toIosSource(), it) }) + + actual val snapshots get() = callbackFlow { + val listener = native.addSnapshotListener { snapshot, error -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val listener = + native.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + actual fun where(filter: Filter) = native.queryWhereFilter(filter.toFIRFilter()) + + private fun Filter.toFIRFilter(): FIRFilter = when (this) { + is Filter.And -> FIRFilter.andFilterWithFilters(filters.map { it.toFIRFilter() }) + is Filter.Or -> FIRFilter.orFilterWithFilters(filters.map { it.toFIRFilter() }) + is Filter.Field -> when (constraint) { + is WhereConstraint.EqualTo -> FIRFilter.filterWhereField(field, isEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereField(field, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.LessThan -> FIRFilter.filterWhereField(field, isLessThan = constraint.safeValue) + is WhereConstraint.GreaterThan -> FIRFilter.filterWhereField(field, isGreaterThan = constraint.safeValue) + is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereField(field, isLessThanOrEqualTo = constraint.safeValue) + is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereField(field, isGreaterThanOrEqualTo = constraint.safeValue) + is WhereConstraint.ArrayContains -> FIRFilter.filterWhereField(field, arrayContains = constraint.safeValue) + is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereField(field, arrayContainsAny = constraint.safeValues) + is WhereConstraint.InArray -> FIRFilter.filterWhereField(field, `in` = constraint.safeValues) + is WhereConstraint.NotInArray -> FIRFilter.filterWhereField(field, notIn = constraint.safeValues) + } + is Filter.Path -> when (constraint) { + is WhereConstraint.EqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.LessThan -> FIRFilter.filterWhereFieldPath(path.ios, isLessThan = constraint.safeValue) + is WhereConstraint.GreaterThan -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThan = constraint.safeValue) + is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isLessThanOrEqualTo = constraint.safeValue) + is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThanOrEqualTo = constraint.safeValue) + is WhereConstraint.ArrayContains -> FIRFilter.filterWhereFieldPath(path.ios, arrayContains = constraint.safeValue) + is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereFieldPath(path.ios, arrayContainsAny = constraint.safeValues) + is WhereConstraint.InArray -> FIRFilter.filterWhereFieldPath(path.ios, `in` = constraint.safeValues) + is WhereConstraint.NotInArray -> FIRFilter.filterWhereFieldPath(path.ios, notIn = constraint.safeValues) + } + } + + actual fun orderBy(field: String, direction: Direction) = native.queryOrderedByField(field, direction == Direction.DESCENDING) + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.queryOrderedByFieldPath(field, direction == Direction.DESCENDING) + + actual fun startAfter(document: NativeDocumentSnapshot) = native.queryStartingAfterDocument(document) + actual fun startAfter(vararg fieldValues: Any) = native.queryStartingAfterValues(fieldValues.asList()) + actual fun startAt(document: NativeDocumentSnapshot) = native.queryStartingAtDocument(document) + actual fun startAt(vararg fieldValues: Any) = native.queryStartingAtValues(fieldValues.asList()) + + actual fun endBefore(document: NativeDocumentSnapshot) = native.queryEndingBeforeDocument(document) + actual fun endBefore(vararg fieldValues: Any) = native.queryEndingBeforeValues(fieldValues.asList()) + actual fun endAt(document: NativeDocumentSnapshot) = native.queryEndingAtDocument(document) + actual fun endAt(vararg fieldValues: Any) = native.queryEndingAtValues(fieldValues.asList()) +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..809275539 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,48 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRTransaction +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.ios +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios + +@PublishedApi +internal actual class NativeTransactionWrapper actual constructor(actual val native: FIRTransaction) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeTransactionWrapper = when (setOptions) { + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) + }.let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeTransactionWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeTransactionWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + actual fun delete(documentRef: DocumentReference) = + native.deleteDocument(documentRef.ios).let { this } + + actual suspend fun get(documentRef: DocumentReference) = + throwError { NativeDocumentSnapshotWrapper(native.getDocument(documentRef.ios, it)!!) } + +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..628096433 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,47 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.firestore.ios +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios + +@PublishedApi +internal actual class NativeWriteBatchWrapper actual constructor(actual val native: NativeWriteBatch) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeWriteBatchWrapper = when (setOptions) { + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) + }.let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeWriteBatchWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeWriteBatchWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios + ).let { this } + + actual fun delete(documentRef: DocumentReference) = + native.deleteDocument(documentRef.ios).let { this } + + actual suspend fun commit() = await { native.commitWithCompletion(it) } +} \ No newline at end of file diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt new file mode 100644 index 000000000..aa9665ac9 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt @@ -0,0 +1,10 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRFirestoreSource +import dev.gitlive.firebase.firestore.Source + +internal fun Source.toIosSource() = when (this) { + Source.CACHE -> FIRFirestoreSource.FIRFirestoreSourceCache + Source.SERVER -> FIRFirestoreSource.FIRFirestoreSourceServer + Source.DEFAULT -> FIRFirestoreSource.FIRFirestoreSourceDefault +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt new file mode 100644 index 000000000..a298833f8 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt @@ -0,0 +1,23 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.toException +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.ObjCObjectVar +import kotlinx.cinterop.alloc +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.ptr +import kotlinx.cinterop.value +import platform.Foundation.NSError + +internal fun T.throwError(block: T.(errorPointer: CPointer>) -> R): R { + memScoped { + val errorPointer: CPointer> = alloc>().ptr + val result = block(errorPointer) + val error: NSError? = errorPointer.pointed.value + if (error != null) { + throw error.toException() + } + return result + } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 418371bb4..f51f9888b 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -10,43 +10,21 @@ import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.externals.getApp import dev.gitlive.firebase.firestore.externals.MemoryCacheSettings import dev.gitlive.firebase.firestore.externals.PersistentCacheSettings -import dev.gitlive.firebase.firestore.externals.QueryConstraint -import dev.gitlive.firebase.firestore.externals.addDoc -import dev.gitlive.firebase.firestore.externals.and -import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence -import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator -import dev.gitlive.firebase.firestore.externals.deleteDoc -import dev.gitlive.firebase.firestore.externals.doc import dev.gitlive.firebase.firestore.externals.getDoc import dev.gitlive.firebase.firestore.externals.getDocFromCache import dev.gitlive.firebase.firestore.externals.getDocFromServer import dev.gitlive.firebase.firestore.externals.getDocs import dev.gitlive.firebase.firestore.externals.getDocsFromCache import dev.gitlive.firebase.firestore.externals.getDocsFromServer -import dev.gitlive.firebase.firestore.externals.initializeFirestore import dev.gitlive.firebase.firestore.externals.memoryEagerGarbageCollector import dev.gitlive.firebase.firestore.externals.memoryLocalCache import dev.gitlive.firebase.firestore.externals.memoryLruGarbageCollector -import dev.gitlive.firebase.firestore.externals.onSnapshot -import dev.gitlive.firebase.firestore.externals.or -import dev.gitlive.firebase.firestore.externals.orderBy import dev.gitlive.firebase.firestore.externals.persistentLocalCache -import dev.gitlive.firebase.firestore.externals.query -import dev.gitlive.firebase.firestore.externals.refEqual -import dev.gitlive.firebase.firestore.externals.setDoc -import dev.gitlive.firebase.firestore.externals.setLogLevel -import dev.gitlive.firebase.firestore.externals.writeBatch -import dev.gitlive.firebase.internal.EncodedObject -import dev.gitlive.firebase.internal.js -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.await -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.promise +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper +import dev.gitlive.firebase.firestore.internal.NativeFirebaseFirestoreWrapper +import dev.gitlive.firebase.firestore.internal.SetOptions import kotlin.js.Json import kotlin.js.json -import dev.gitlive.firebase.externals.FirebaseApp as JsFirebaseApp import dev.gitlive.firebase.firestore.externals.Firestore as JsFirestore import dev.gitlive.firebase.firestore.externals.CollectionReference as JsCollectionReference import dev.gitlive.firebase.firestore.externals.DocumentChange as JsDocumentChange @@ -58,19 +36,7 @@ import dev.gitlive.firebase.firestore.externals.QuerySnapshot as JsQuerySnapshot import dev.gitlive.firebase.firestore.externals.SnapshotMetadata as JsSnapshotMetadata import dev.gitlive.firebase.firestore.externals.Transaction as JsTransaction import dev.gitlive.firebase.firestore.externals.WriteBatch as JsWriteBatch -import dev.gitlive.firebase.firestore.externals.collection as jsCollection -import dev.gitlive.firebase.firestore.externals.collectionGroup as jsCollectionGroup -import dev.gitlive.firebase.firestore.externals.disableNetwork as jsDisableNetwork import dev.gitlive.firebase.firestore.externals.documentId as jsDocumentId -import dev.gitlive.firebase.firestore.externals.enableNetwork as jsEnableNetwork -import dev.gitlive.firebase.firestore.externals.endAt as jsEndAt -import dev.gitlive.firebase.firestore.externals.endBefore as jsEndBefore -import dev.gitlive.firebase.firestore.externals.limit as jsLimit -import dev.gitlive.firebase.firestore.externals.runTransaction as jsRunTransaction -import dev.gitlive.firebase.firestore.externals.startAfter as jsStartAfter -import dev.gitlive.firebase.firestore.externals.startAt as jsStartAt -import dev.gitlive.firebase.firestore.externals.updateDoc as jsUpdate -import dev.gitlive.firebase.firestore.externals.where as jsWhere actual val Firebase.firestore get() = rethrow { FirebaseFirestore(NativeFirebaseFirestoreWrapper(getApp())) } @@ -80,76 +46,6 @@ actual fun Firebase.firestore(app: FirebaseApp) = actual data class NativeFirebaseFirestore(val js: JsFirestore) -internal actual class NativeFirebaseFirestoreWrapper internal constructor( - private val createNative: NativeFirebaseFirestoreWrapper.() -> NativeFirebaseFirestore -){ - - internal actual constructor(native: NativeFirebaseFirestore) : this({ native }) - internal constructor(app: JsFirebaseApp) : this( - { - NativeFirebaseFirestore( - initializeFirestore(app, settings.js).also { - emulatorSettings?.run { - connectFirestoreEmulator(it, host, port) - } - } - ) - } - ) - - private data class EmulatorSettings(val host: String, val port: Int) - - actual var settings: FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().build() - set(value) { - if (lazyNative.isInitialized()) { - throw IllegalStateException("FirebaseFirestore has already been started and its settings can no longer be changed. You can only call setFirestoreSettings() before calling any other methods on a FirebaseFirestore object.") - } else { - field = value - } - } - private var emulatorSettings: EmulatorSettings? = null - - // initializeFirestore must be called before any call, including before `getFirestore()` - // To allow settings to be updated, we defer creating the wrapper until the first call to `native` - private val lazyNative = lazy { - createNative() - } - actual val native: NativeFirebaseFirestore by lazyNative - private val js get() = native.js - - actual fun collection(collectionPath: String) = rethrow { NativeCollectionReference(jsCollection(js, collectionPath)) } - - actual fun collectionGroup(collectionId: String) = rethrow { NativeQuery(jsCollectionGroup(js, collectionId)) } - - actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } - - actual fun batch() = rethrow { NativeWriteBatch(writeBatch(js)) } - - actual fun setLoggingEnabled(loggingEnabled: Boolean) = - rethrow { setLogLevel( if(loggingEnabled) "error" else "silent") } - - actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = - rethrow { jsRunTransaction(js, { GlobalScope.promise { NativeTransaction(it).func() } } ).await() } - - actual suspend fun clearPersistence() = - rethrow { clearIndexedDbPersistence(js).await() } - - actual fun useEmulator(host: String, port: Int) = rethrow { - settings = firestoreSettings(settings) { - this.host = "$host:$port" - } - emulatorSettings = EmulatorSettings(host, port) - } - - actual suspend fun disableNetwork() { - rethrow { jsDisableNetwork(js).await() } - } - - actual suspend fun enableNetwork() { - rethrow { jsEnableNetwork(js).await() } - } -} - val FirebaseFirestore.js: JsFirestore get() = native.js actual data class FirebaseFirestoreSettings( @@ -215,297 +111,26 @@ actual fun firestoreSettings( } }.apply(builder).build() -internal val SetOptions.js: Json get() = when (this) { - is SetOptions.Merge -> json("merge" to true) - is SetOptions.Overwrite -> json("merge" to false) - is SetOptions.MergeFields -> json("mergeFields" to fields.toTypedArray()) - is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) -} - actual data class NativeWriteBatch(val js: JsWriteBatch) -@PublishedApi -internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { - - constructor(js: JsWriteBatch) : this(NativeWriteBatch(js)) - - val js = native.js - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.js, setOptions.js) }.let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.js) } - .let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatchWrapper = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeWriteBatchWrapper = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - rethrow { js.delete(documentRef.js) } - .let { this } - - actual suspend fun commit() = rethrow { js.commit().await() } -} - val WriteBatch.js get() = native.js actual data class NativeTransaction(val js: JsTransaction) -@PublishedApi -internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { - - constructor(js: JsTransaction) : this(NativeTransaction(js)) - - val js = native.js - - actual fun setEncoded( - documentRef: DocumentReference, - encodedData: EncodedObject, - setOptions: SetOptions - ): NativeTransactionWrapper = rethrow { - js.set(documentRef.js, encodedData.js, setOptions.js) - } - .let { this } - - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.js) } - .let { this } - - actual fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransactionWrapper = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): NativeTransactionWrapper = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - rethrow { js.delete(documentRef.js) } - .let { this } - - actual suspend fun get(documentRef: DocumentReference) = - rethrow { NativeDocumentSnapshotWrapper(js.get(documentRef.js).await()) } -} - val Transaction.js get() = native.js /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = JsDocumentReference -@PublishedApi -internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { - val js: NativeDocumentReferenceType = nativeValue - - actual val id: String - get() = rethrow { js.id } - - actual val path: String - get() = rethrow { js.path } - - actual val parent: NativeCollectionReferenceWrapper - get() = rethrow { NativeCollectionReferenceWrapper(js.parent) } - - actual fun collection(collectionPath: String) = rethrow { NativeCollectionReference(jsCollection(js, collectionPath)) } - - actual suspend fun get(source: Source) = rethrow { NativeDocumentSnapshot( js.get(source).await()) } - - actual val snapshots: Flow get() = snapshots() - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val unsubscribe = onSnapshot( - js, - json("includeMetadataChanges" to includeMetadataChanges), - { trySend(NativeDocumentSnapshot(it)) }, - { close(errorToException(it)) } - ) - awaitClose { unsubscribe() } - } - - actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { - setDoc(js, encodedData.js, setOptions.js).await() - } - - actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData.js).await() } - - actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { - rethrow { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - jsUpdate(js, field, value, *moreFieldsAndValues) - } - ?.await() - } - } - - actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { - rethrow { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - jsUpdate(js, field, value, *moreFieldsAndValues) - }?.await() - } - } - - actual suspend fun delete() = rethrow { deleteDoc(js).await() } - - override fun equals(other: Any?): Boolean = - this === other || other is NativeDocumentReference && refEqual(nativeValue, other.nativeValue) - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = "DocumentReference(path=$path)" -} - val DocumentReference.js get() = native.js actual open class NativeQuery(open val js: JsQuery) internal val JsQuery.wrapped get() = NativeQuery(this) -@PublishedApi -internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { - - constructor(js: JsQuery) : this(NativeQuery(js)) - - open val js: JsQuery get() = native.js - - actual suspend fun get(source: Source) = rethrow { QuerySnapshot(js.get(source).await()) } - - actual fun limit(limit: Number) = query(js, jsLimit(limit)).wrapped - - actual fun where(filter: Filter) = query(js, filter.toQueryConstraint()).wrapped - - private fun Filter.toQueryConstraint(): QueryConstraint = when (this) { - is Filter.And -> and(*filters.map { it.toQueryConstraint() }.toTypedArray()) - is Filter.Or -> or(*filters.map { it.toQueryConstraint() }.toTypedArray()) - is Filter.Field -> { - val value = when (constraint) { - is WhereConstraint.ForNullableObject -> constraint.safeValue - is WhereConstraint.ForObject -> constraint.safeValue - is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() - } - jsWhere(field, constraint.filterOp, value) - } - is Filter.Path -> { - val value = when (constraint) { - is WhereConstraint.ForNullableObject -> constraint.safeValue - is WhereConstraint.ForObject -> constraint.safeValue - is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() - } - jsWhere(path.js, constraint.filterOp, value) - } - } - - private val WhereConstraint.filterOp: String get() = when (this) { - is WhereConstraint.EqualTo -> "==" - is WhereConstraint.NotEqualTo -> "!=" - is WhereConstraint.LessThan -> "<" - is WhereConstraint.LessThanOrEqualTo -> "<=" - is WhereConstraint.GreaterThan -> ">" - is WhereConstraint.GreaterThanOrEqualTo -> ">=" - is WhereConstraint.ArrayContains -> "array-contains" - is WhereConstraint.ArrayContainsAny -> "array-contains-any" - is WhereConstraint.InArray -> "in" - is WhereConstraint.NotInArray -> "not-in" - } - - actual fun orderBy(field: String, direction: Direction) = rethrow { - query(js, orderBy(field, direction.jsString)).wrapped - } - - actual fun orderBy(field: EncodedFieldPath, direction: Direction) = rethrow { - query(js, orderBy(field, direction.jsString)).wrapped - } - - actual fun startAfter(document: NativeDocumentSnapshot) = rethrow { query(js, jsStartAfter(document.js)).wrapped } - - actual fun startAfter(vararg fieldValues: Any) = rethrow { query(js, jsStartAfter(*fieldValues)).wrapped } - - actual fun startAt(document: NativeDocumentSnapshot) = rethrow { query(js, jsStartAt(document.js)).wrapped } - - actual fun startAt(vararg fieldValues: Any) = rethrow { query(js, jsStartAt(*fieldValues)).wrapped } - - actual fun endBefore(document: NativeDocumentSnapshot) = rethrow { query(js, jsEndBefore(document.js)).wrapped } - - actual fun endBefore(vararg fieldValues: Any) = rethrow { query(js, jsEndBefore(*fieldValues)).wrapped } - - actual fun endAt(document: NativeDocumentSnapshot) = rethrow { query(js, jsEndAt(document.js)).wrapped } - - actual fun endAt(vararg fieldValues: Any) = rethrow { query(js, jsEndAt(*fieldValues)).wrapped } - - actual val snapshots get() = callbackFlow { - val unsubscribe = rethrow { - onSnapshot( - js, - { trySend(QuerySnapshot(it)) }, - { close(errorToException(it)) } - ) - } - awaitClose { rethrow { unsubscribe() } } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val unsubscribe = rethrow { - onSnapshot( - js, - json("includeMetadataChanges" to includeMetadataChanges), - { trySend(QuerySnapshot(it)) }, - { close(errorToException(it)) } - ) - } - awaitClose { rethrow { unsubscribe() } } - } -} - val Query.js get() = native.js actual data class NativeCollectionReference(override val js: JsCollectionReference) : NativeQuery(js) -@PublishedApi -internal actual class NativeCollectionReferenceWrapper actual internal constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { - - constructor(js: JsCollectionReference) : this(NativeCollectionReference(js)) - - override val js: JsCollectionReference = native.js - - actual val path: String - get() = rethrow { js.path } - - actual val document get() = rethrow { NativeDocumentReference(doc(js)) } - - actual val parent get() = rethrow { js.parent?.let{ NativeDocumentReference(it) } } - - actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } - - actual suspend fun addEncoded(data: EncodedObject) = rethrow { - NativeDocumentReference(addDoc(js, data.js).await()) - } -} - val CollectionReference.js get() = native.js actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) @@ -534,37 +159,6 @@ actual class DocumentChange(val js: JsDocumentChange) { actual data class NativeDocumentSnapshot(val js: JsDocumentSnapshot) -@PublishedApi -internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: NativeDocumentSnapshot) { - - constructor(js: JsDocumentSnapshot) : this(NativeDocumentSnapshot(js)) - - val js: JsDocumentSnapshot = native.js - - actual val id get() = rethrow { js.id } - actual val reference get() = rethrow { NativeDocumentReference(js.ref) } - - actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { - js.get(field, getTimestampsOptions(serverTimestampBehavior)) - } - - actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { - js.get(fieldPath, getTimestampsOptions(serverTimestampBehavior)) - } - - actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { - js.data(getTimestampsOptions(serverTimestampBehavior)) - } - - actual fun contains(field: String) = rethrow { js.get(field) != undefined } - actual fun contains(fieldPath: EncodedFieldPath) = rethrow { js.get(fieldPath) != undefined } - actual val exists get() = rethrow { js.exists() } - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) - - fun getTimestampsOptions(serverTimestampBehavior: ServerTimestampBehavior) = - json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) -} - val DocumentSnapshot.js get() = native.js actual class SnapshotMetadata(val js: JsSnapshotMetadata) { @@ -589,14 +183,6 @@ actual class FieldPath private constructor(val js: JsFieldPath) { actual typealias EncodedFieldPath = JsFieldPath -//actual data class FirebaseFirestoreSettings internal constructor( -// val cacheSizeBytes: Number? = undefined, -// val host: String? = undefined, -// val ssl: Boolean? = undefined, -// var timestampsInSnapshots: Boolean? = undefined, -// var enablePersistence: Boolean = false -//) - actual enum class FirestoreExceptionCode { OK, CANCELLED, @@ -677,15 +263,3 @@ fun entriesOf(jsObject: dynamic): List> = // from: https://discuss.kotlinlang.org/t/how-to-access-native-js-object-as-a-map-string-any/509/8 fun mapOf(jsObject: dynamic): Map = entriesOf(jsObject).toMap() - -private fun NativeDocumentReferenceType.get(source: Source) = when (source) { - Source.DEFAULT -> getDoc(this) - Source.CACHE -> getDocFromCache(this) - Source.SERVER -> getDocFromServer(this) -} - -private fun JsQuery.get(source: Source) = when (source) { - Source.DEFAULT -> getDocs(this) - Source.CACHE -> getDocsFromCache(this) - Source.SERVER -> getDocsFromServer(this) -} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/JSQueryGet.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/JSQueryGet.kt new file mode 100644 index 000000000..51524558a --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/JSQueryGet.kt @@ -0,0 +1,7 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.externals.Query +import dev.gitlive.firebase.firestore.externals.getDocs +import dev.gitlive.firebase.firestore.externals.getDocsFromCache +import dev.gitlive.firebase.firestore.externals.getDocsFromServer diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..f3f475006 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,38 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.externals.CollectionReference +import dev.gitlive.firebase.firestore.externals.addDoc +import dev.gitlive.firebase.firestore.externals.doc +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await + +@PublishedApi +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + constructor(js: CollectionReference) : this(NativeCollectionReference(js)) + + override val js: CollectionReference = native.js + + actual val path: String + get() = rethrow { js.path } + + actual val document get() = rethrow { NativeDocumentReference(doc(js)) } + + actual val parent get() = rethrow { js.parent?.let{ NativeDocumentReference(it) } } + + actual fun document(documentPath: String) = rethrow { + NativeDocumentReference( + doc( + js, + documentPath + ) + ) + } + + actual suspend fun addEncoded(data: EncodedObject) = rethrow { + NativeDocumentReference(addDoc(js, data.js).await()) + } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..f38a8faf0 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,110 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.errorToException +import dev.gitlive.firebase.firestore.externals.deleteDoc +import dev.gitlive.firebase.firestore.externals.getDoc +import dev.gitlive.firebase.firestore.externals.getDocFromCache +import dev.gitlive.firebase.firestore.externals.getDocFromServer +import dev.gitlive.firebase.firestore.externals.onSnapshot +import dev.gitlive.firebase.firestore.externals.refEqual +import dev.gitlive.firebase.firestore.externals.setDoc +import dev.gitlive.firebase.firestore.externals.updateDoc +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlin.js.json + +@PublishedApi +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + val js: NativeDocumentReferenceType = nativeValue + + actual val id: String + get() = rethrow { js.id } + + actual val path: String + get() = rethrow { js.path } + + actual val parent: NativeCollectionReferenceWrapper + get() = rethrow { NativeCollectionReferenceWrapper(js.parent) } + + actual fun collection(collectionPath: String) = rethrow { + NativeCollectionReference( + dev.gitlive.firebase.firestore.externals.collection( + js, + collectionPath + ) + ) + } + + actual suspend fun get(source: Source) = rethrow { + NativeDocumentSnapshot( + js.get(source).await() + ) + } + + actual val snapshots: Flow get() = snapshots() + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val unsubscribe = onSnapshot( + js, + json("includeMetadataChanges" to includeMetadataChanges), + { trySend(NativeDocumentSnapshot(it)) }, + { close(errorToException(it)) } + ) + awaitClose { unsubscribe() } + } + + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { + setDoc(js, encodedData.js, setOptions.js).await() + } + + actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { updateDoc( + js, + encodedData.js + ).await() } + + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + rethrow { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + updateDoc(js, field, value, *moreFieldsAndValues) + } + ?.await() + } + } + + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + rethrow { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + updateDoc(js, field, value, *moreFieldsAndValues) + }?.await() + } + } + + actual suspend fun delete() = rethrow { deleteDoc(js).await() } + + override fun equals(other: Any?): Boolean = + this === other || other is NativeDocumentReference && refEqual( + nativeValue, + other.nativeValue + ) + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = "DocumentReference(path=$path)" +} + +private fun NativeDocumentReferenceType.get(source: Source) = when (source) { + Source.DEFAULT -> getDoc(this) + Source.CACHE -> getDocFromCache(this) + Source.SERVER -> getDocFromServer(this) +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..dfc83769a --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,40 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata +import dev.gitlive.firebase.firestore.externals.DocumentSnapshot +import dev.gitlive.firebase.firestore.rethrow +import kotlin.js.json + +@PublishedApi +internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: NativeDocumentSnapshot) { + + constructor(js: DocumentSnapshot) : this(NativeDocumentSnapshot(js)) + + val js: DocumentSnapshot = native.js + + actual val id get() = rethrow { js.id } + actual val reference get() = rethrow { NativeDocumentReference(js.ref) } + + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.get(field, getTimestampsOptions(serverTimestampBehavior)) + } + + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.get(fieldPath, getTimestampsOptions(serverTimestampBehavior)) + } + + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.data(getTimestampsOptions(serverTimestampBehavior)) + } + + actual fun contains(field: String) = rethrow { js.get(field) != undefined } + actual fun contains(fieldPath: EncodedFieldPath) = rethrow { js.get(fieldPath) != undefined } + actual val exists get() = rethrow { js.exists() } + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) + + fun getTimestampsOptions(serverTimestampBehavior: ServerTimestampBehavior) = + json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..4c186869d --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,113 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.externals.FirebaseApp +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence +import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator +import dev.gitlive.firebase.firestore.externals.doc +import dev.gitlive.firebase.firestore.externals.initializeFirestore +import dev.gitlive.firebase.firestore.externals.setLogLevel +import dev.gitlive.firebase.firestore.externals.writeBatch +import dev.gitlive.firebase.firestore.firestoreSettings +import dev.gitlive.firebase.firestore.rethrow +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.await +import kotlinx.coroutines.promise + +internal actual class NativeFirebaseFirestoreWrapper internal constructor( + private val createNative: NativeFirebaseFirestoreWrapper.() -> NativeFirebaseFirestore +){ + + internal actual constructor(native: NativeFirebaseFirestore) : this({ native }) + internal constructor(app: FirebaseApp) : this( + { + NativeFirebaseFirestore( + initializeFirestore(app, settings.js).also { + emulatorSettings?.run { + connectFirestoreEmulator(it, host, port) + } + } + ) + } + ) + + private data class EmulatorSettings(val host: String, val port: Int) + + actual var settings: FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().build() + set(value) { + if (lazyNative.isInitialized()) { + throw IllegalStateException("FirebaseFirestore has already been started and its settings can no longer be changed. You can only call setFirestoreSettings() before calling any other methods on a FirebaseFirestore object.") + } else { + field = value + } + } + private var emulatorSettings: EmulatorSettings? = null + + // initializeFirestore must be called before any call, including before `getFirestore()` + // To allow settings to be updated, we defer creating the wrapper until the first call to `native` + private val lazyNative = lazy { + createNative() + } + actual val native: NativeFirebaseFirestore by lazyNative + private val js get() = native.js + + actual fun collection(collectionPath: String) = rethrow { + NativeCollectionReference( + dev.gitlive.firebase.firestore.externals.collection( + js, + collectionPath + ) + ) + } + + actual fun collectionGroup(collectionId: String) = rethrow { + NativeQuery( + dev.gitlive.firebase.firestore.externals.collectionGroup( + js, + collectionId + ) + ) + } + + actual fun document(documentPath: String) = rethrow { + NativeDocumentReference( + doc( + js, + documentPath + ) + ) + } + + actual fun batch() = rethrow { NativeWriteBatch(writeBatch(js)) } + + actual fun setLoggingEnabled(loggingEnabled: Boolean) = + rethrow { setLogLevel(if (loggingEnabled) "error" else "silent") } + + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = + rethrow { dev.gitlive.firebase.firestore.externals.runTransaction( + js, + { GlobalScope.promise { NativeTransaction(it).func() } }).await() } + + actual suspend fun clearPersistence() = + rethrow { clearIndexedDbPersistence(js).await() } + + actual fun useEmulator(host: String, port: Int) = rethrow { + settings = firestoreSettings(settings) { + this.host = "$host:$port" + } + emulatorSettings = EmulatorSettings(host, port) + } + + actual suspend fun disableNetwork() { + rethrow { dev.gitlive.firebase.firestore.externals.disableNetwork(js).await() } + } + + actual suspend fun enableNetwork() { + rethrow { dev.gitlive.firebase.firestore.externals.enableNetwork(js).await() } + } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..61c98df06 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,154 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.WhereConstraint +import dev.gitlive.firebase.firestore.errorToException +import dev.gitlive.firebase.firestore.externals.Query +import dev.gitlive.firebase.firestore.externals.QueryConstraint +import dev.gitlive.firebase.firestore.externals.and +import dev.gitlive.firebase.firestore.externals.getDocs +import dev.gitlive.firebase.firestore.externals.getDocsFromCache +import dev.gitlive.firebase.firestore.externals.getDocsFromServer +import dev.gitlive.firebase.firestore.externals.onSnapshot +import dev.gitlive.firebase.firestore.externals.or +import dev.gitlive.firebase.firestore.externals.query +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.firestore.wrapped +import kotlinx.coroutines.await +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlin.js.json + +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { + + constructor(js: Query) : this(NativeQuery(js)) + + open val js: Query get() = native.js + + actual suspend fun get(source: Source) = rethrow { QuerySnapshot(js.get(source).await()) } + + actual fun limit(limit: Number) = query( + js, + dev.gitlive.firebase.firestore.externals.limit(limit) + ).wrapped + + actual fun where(filter: Filter) = query(js, filter.toQueryConstraint()).wrapped + + private fun Filter.toQueryConstraint(): QueryConstraint = when (this) { + is Filter.And -> and(*filters.map { it.toQueryConstraint() }.toTypedArray()) + is Filter.Or -> or(*filters.map { it.toQueryConstraint() }.toTypedArray()) + is Filter.Field -> { + val value = when (constraint) { + is WhereConstraint.ForNullableObject -> constraint.safeValue + is WhereConstraint.ForObject -> constraint.safeValue + is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() + } + dev.gitlive.firebase.firestore.externals.where(field, constraint.filterOp, value) + } + is Filter.Path -> { + val value = when (constraint) { + is WhereConstraint.ForNullableObject -> constraint.safeValue + is WhereConstraint.ForObject -> constraint.safeValue + is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() + } + dev.gitlive.firebase.firestore.externals.where(path.js, constraint.filterOp, value) + } + } + + private val WhereConstraint.filterOp: String get() = when (this) { + is WhereConstraint.EqualTo -> "==" + is WhereConstraint.NotEqualTo -> "!=" + is WhereConstraint.LessThan -> "<" + is WhereConstraint.LessThanOrEqualTo -> "<=" + is WhereConstraint.GreaterThan -> ">" + is WhereConstraint.GreaterThanOrEqualTo -> ">=" + is WhereConstraint.ArrayContains -> "array-contains" + is WhereConstraint.ArrayContainsAny -> "array-contains-any" + is WhereConstraint.InArray -> "in" + is WhereConstraint.NotInArray -> "not-in" + } + + actual fun orderBy(field: String, direction: Direction) = rethrow { + query(js, dev.gitlive.firebase.firestore.externals.orderBy(field, direction.jsString)).wrapped + } + + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = rethrow { + query(js, dev.gitlive.firebase.firestore.externals.orderBy(field, direction.jsString)).wrapped + } + + actual fun startAfter(document: NativeDocumentSnapshot) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.startAfter(document.js) + ).wrapped } + + actual fun startAfter(vararg fieldValues: Any) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.startAfter(*fieldValues) + ).wrapped } + + actual fun startAt(document: NativeDocumentSnapshot) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.startAt(document.js) + ).wrapped } + + actual fun startAt(vararg fieldValues: Any) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.startAt(*fieldValues) + ).wrapped } + + actual fun endBefore(document: NativeDocumentSnapshot) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.endBefore(document.js) + ).wrapped } + + actual fun endBefore(vararg fieldValues: Any) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.endBefore(*fieldValues) + ).wrapped } + + actual fun endAt(document: NativeDocumentSnapshot) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.endAt(document.js) + ).wrapped } + + actual fun endAt(vararg fieldValues: Any) = rethrow { query( + js, + dev.gitlive.firebase.firestore.externals.endAt(*fieldValues) + ).wrapped } + + actual val snapshots get() = callbackFlow { + val unsubscribe = rethrow { + onSnapshot( + js, + { trySend(QuerySnapshot(it)) }, + { close(errorToException(it)) } + ) + } + awaitClose { rethrow { unsubscribe() } } + } + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val unsubscribe = rethrow { + onSnapshot( + js, + json("includeMetadataChanges" to includeMetadataChanges), + { trySend(QuerySnapshot(it)) }, + { close(errorToException(it)) } + ) + } + awaitClose { rethrow { unsubscribe() } } + } +} + +private fun Query.get(source: Source) = when (source) { + Source.DEFAULT -> getDocs(this) + Source.CACHE -> getDocsFromCache(this) + Source.SERVER -> getDocsFromServer(this) +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..9bcabf42d --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,57 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.externals.Transaction +import dev.gitlive.firebase.firestore.js +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await + +@PublishedApi +internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { + + constructor(js: Transaction) : this(NativeTransaction(js)) + + val js = native.js + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeTransactionWrapper = rethrow { + js.set(documentRef.js, encodedData.js, setOptions.js) + } + .let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.js) } + .let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeTransactionWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeTransactionWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + rethrow { js.delete(documentRef.js) } + .let { this } + + actual suspend fun get(documentRef: DocumentReference) = + rethrow { NativeDocumentSnapshotWrapper(js.get(documentRef.js).await()) } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..819e563d1 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,53 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.externals.WriteBatch +import dev.gitlive.firebase.firestore.js +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await + +@PublishedApi +internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { + + constructor(js: WriteBatch) : this(NativeWriteBatch(js)) + + val js = native.js + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions + ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.js, setOptions.js) }.let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.js) } + .let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeWriteBatchWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List> + ): NativeWriteBatchWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + rethrow { js.delete(documentRef.js) } + .let { this } + + actual suspend fun commit() = rethrow { js.commit().await() } +} \ No newline at end of file diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt new file mode 100644 index 000000000..3857ad03a --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt @@ -0,0 +1,12 @@ +package dev.gitlive.firebase.firestore.internal + +import kotlin.js.Json +import kotlin.js.json + +internal val SetOptions.js: Json + get() = when (this) { + is SetOptions.Merge -> json("merge" to true) + is SetOptions.Overwrite -> json("merge" to false) + is SetOptions.MergeFields -> json("mergeFields" to fields.toTypedArray()) + is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) +}