From dc547148170d037b4bd93b3d352c7596a4f809e9 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 12 Apr 2024 01:03:22 +0200 Subject: [PATCH] More stable implementation of Firestore encoding Make methods stricter and fail with Library exceptions so they can be caught consistently. --- .../kotlin/dev/gitlive/firebase/_encoders.kt | 22 ++- .../kotlin/dev/gitlive/firebase/encoders.kt | 38 +++-- .../kotlin/dev/gitlive/firebase/_encoders.kt | 20 +-- .../kotlin/dev/gitlive/firebase/_encoders.kt | 31 ++-- .../dev/gitlive/firebase/database/database.kt | 4 +- .../gitlive/firebase/firestore/firestore.kt | 132 +++++++++--------- .../gitlive/firebase/firestore/firestore.kt | 106 ++++++++------ .../gitlive/firebase/firestore/firestore.kt | 79 +++++------ .../gitlive/firebase/firestore/firestore.kt | 108 ++++++++------ 9 files changed, 302 insertions(+), 238 deletions(-) diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt index bcfec9152..a13e9a859 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,22 +10,18 @@ import kotlinx.serialization.descriptors.StructureKind import java.lang.IllegalArgumentException import kotlin.collections.set -actual data class EncodedObject( - val android: Map -) +actual data class EncodedObject(actual val raw: Map) { + actual companion object { + actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) + } + val android: Map get() = raw +} -actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) +@PublishedApi +internal actual fun List>.asEncodedObject() = EncodedObject(toMap()) @PublishedApi -internal actual fun Map<*, *>.asEncodedObject() = EncodedObject( - map { (key, value) -> - if (key is String) { - key to value - } else { - throw IllegalArgumentException("Expected a String key but received $key") - } - }.toMap() -) +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> mutableListOf() diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt index 363ecbaa6..a74f2d24f 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt @@ -11,9 +11,13 @@ import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule -expect class EncodedObject +expect class EncodedObject { + companion object { + val emptyEncodedObject: EncodedObject + } -expect val emptyEncodedObject: EncodedObject + val raw: Map +} @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(strategy, value) { encodeDefaults = shouldEncodeElementDefault }")) fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = encode(strategy, value) { @@ -36,20 +40,12 @@ inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings()) inline fun encodeAsObject(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { - val encoded = encode(strategy, value, buildSettings) - return when (encoded) { - is Map<*, *> -> encoded.asEncodedObject() - null -> throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") - else -> throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") - } + val encoded = encode(strategy, value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") } inline fun encodeAsObject(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { - val encoded = encode(value, buildSettings) - return when (encoded) { - is Map<*, *> -> encoded.asEncodedObject() - null -> throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") - else -> throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") - } + val encoded = encode(value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") } @PublishedApi @@ -67,7 +63,19 @@ internal inline fun encode(value: T, encodeSettings: EncodeSettings) } @PublishedApi -internal expect fun Map<*, *>.asEncodedObject(): EncodedObject +expect internal fun Any.asNativeMap(): Map<*, *>? + +@PublishedApi +internal fun Map<*, *>.asEncodedObject(): EncodedObject = map { (key, value) -> + if (key is String) { + key to value + } else { + throw IllegalArgumentException("Expected a String key but received $key") + } +}.asEncodedObject() + +@PublishedApi +internal expect fun List>.asEncodedObject(): EncodedObject /** * An extension which which serializer to use for value. Handy in updating fields by name or path diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt index 58cecc4d9..da99abbdc 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -9,18 +9,20 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual data class EncodedObject( - val ios: Map -) +actual data class EncodedObject(actual val raw: Map) { + actual companion object { + actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) + } -actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyMap()) + val ios: Map get() = raw.mapKeys { (key, _) -> key as? Any } +} @PublishedApi -internal actual fun Map<*, *>.asEncodedObject() = EncodedObject( - map { (key, value) -> - key to value - }.toMap() -) +internal actual fun List>.asEncodedObject() = EncodedObject(toMap()) + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> + actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> encodeAsList() StructureKind.MAP -> mutableListOf() diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt index 547f8b5af..00cafbce8 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt @@ -10,19 +10,28 @@ import kotlinx.serialization.descriptors.StructureKind import kotlin.js.Json import kotlin.js.json -actual typealias EncodedObject = Json +actual data class EncodedObject(private val keyValues: List>) { + actual companion object { + actual val emptyEncodedObject: EncodedObject = EncodedObject(emptyList()) + } + + actual val raw get() = keyValues.toMap() + val json get() = json(*keyValues.toTypedArray()) +} + -actual val emptyEncodedObject: EncodedObject = json() @PublishedApi -internal actual fun Map<*, *>.asEncodedObject() = json( - *map { (key, value) -> - if (key is String) { - key to value - } else { - throw IllegalArgumentException("Expected a String key but received $key") - } - }.toTypedArray() -) +internal actual fun List>.asEncodedObject() = EncodedObject(this) + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = (this as? Json)?.let { json -> + val mutableMap = mutableMapOf() + for (key in js("Object").keys(json)) { + mutableMap[key] = json[key] + } + mutableMap.toMap() +} + actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { StructureKind.LIST -> encodeAsList(descriptor) StructureKind.MAP -> { diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index c67edf051..4e48d612c 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -184,7 +184,7 @@ internal actual class NativeDatabaseReference internal constructor( } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { update(js, encodedUpdate).awaitWhileOnline(database) } + rethrow { update(js, encodedUpdate.json).awaitWhileOnline(database) } actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { @@ -236,7 +236,7 @@ internal actual class NativeOnDisconnect internal constructor( rethrow { js.set(encodedValue).awaitWhileOnline(database) } actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = - rethrow { js.update(encodedUpdate).awaitWhileOnline(database) } + rethrow { js.update(encodedUpdate.json).awaitWhileOnline(database) } } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 1e7a557f8..c82d12440 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -28,7 +28,7 @@ actual class FirebaseFirestore(val android: com.google.firebase.firestore.Fireba actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReferenceWrapper(android.collection(collectionPath))) - actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId).native) + actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId).wrapped) actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(android.document(documentPath))) @@ -74,84 +74,87 @@ internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) } +actual typealias NativeWriteBatch = com.google.firebase.firestore.WriteBatch + @PublishedApi -internal actual class NativeWriteBatchWrapper(val android: com.google.firebase.firestore.WriteBatch) { +internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions ): NativeWriteBatchWrapper = (setOptions.android?.let { - android.set(documentRef.android, encodedData, it) - } ?: android.set(documentRef.android, encodedData)).let { + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android)).let { this } - @Suppress("UNCHECKED_CAST") - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any) = android.update(documentRef.android, encodedData as Map).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) + native.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) + native.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } + native.delete(documentRef.android).let { this } actual suspend fun commit() { - android.commit().await() + native.commit().await() } } -val WriteBatch.android get() = native.android +val WriteBatch.android get() = native + +actual typealias NativeTransaction = com.google.firebase.firestore.Transaction @PublishedApi -internal actual class NativeTransactionWrapper(val android: com.google.firebase.firestore.Transaction) { +internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions ): NativeTransactionWrapper { setOptions.android?.let { - android.set(documentRef.android, encodedData, it) - } ?: android.set(documentRef.android, encodedData) + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android) return this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = android.update(documentRef.android, encodedData.android).let { this } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } actual fun updateEncodedFieldsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) + native.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } actual fun updateEncodedFieldPathsAndValues( documentRef: DocumentReference, encodedFieldsAndValues: List> ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) + native.update(documentRef.android, field, value, *moreFieldsAndValues) }.let { this } actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } + native.delete(documentRef.android).let { this } actual suspend fun get(documentRef: DocumentReference) = - NativeDocumentSnapshotWrapper(android.get(documentRef.android)) + NativeDocumentSnapshotWrapper(native.get(documentRef.android)) } -val Transaction.android get() = nativeWrapper.android +val Transaction.android get() = native /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = com.google.firebase.firestore.DocumentReference @@ -220,20 +223,16 @@ internal actual class NativeDocumentReference actual constructor(actual val nati val DocumentReference.android get() = native.android -@PublishedApi -internal actual open class NativeQueryWrapper(open val android: AndroidQuery) -internal val AndroidQuery.native get() = NativeQueryWrapper(this) - -actual open class Query internal actual constructor(nativeWrapper: NativeQueryWrapper) { +actual typealias NativeQuery = AndroidQuery - open val android = nativeWrapper.android - - actual suspend fun get() = QuerySnapshot(android.get().await()) +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: AndroidQuery) { + actual suspend fun get() = QuerySnapshot(native.get().await()) - actual fun limit(limit: Number) = Query(NativeQueryWrapper(android.limit(limit.toLong()))) + actual fun limit(limit: Number) = native.limit(limit.toLong()).wrapped actual val snapshots get() = callbackFlow { - val listener = android.addSnapshotListener { snapshot, exception -> + val listener = native.addSnapshotListener { snapshot, exception -> snapshot?.let { trySend(QuerySnapshot(snapshot)) } exception?.let { close(exception) } } @@ -242,16 +241,14 @@ actual open class Query internal actual constructor(nativeWrapper: NativeQueryWr actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> + val listener = native.addSnapshotListener(metadataChanges) { snapshot, exception -> snapshot?.let { trySend(QuerySnapshot(snapshot)) } exception?.let { close(exception) } } awaitClose { listener.remove() } } - internal actual fun where(filter: Filter) = Query( - android.where(filter.toAndroidFilter()).native - ) + actual fun where(filter: Filter) = native.where(filter.toAndroidFilter()).wrapped private fun Filter.toAndroidFilter(): AndroidFilter = when (this) { is Filter.And -> AndroidFilter.and(*filters.map { it.toAndroidFilter() }.toTypedArray()) @@ -316,41 +313,47 @@ actual open class Query internal actual constructor(nativeWrapper: NativeQueryWr } } - internal actual fun _orderBy(field: String, direction: Direction) = Query(android.orderBy(field, direction).native) - internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(android.orderBy(field.android, direction).native) + actual fun orderBy(field: String, direction: Direction) = native.orderBy(field, direction).wrapped + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.orderBy(field, direction).wrapped - internal actual fun _startAfter(document: DocumentSnapshot) = Query(android.startAfter(document.android).native) - internal actual fun _startAfter(vararg fieldValues: Any) = Query(android.startAfter(*fieldValues).native) - internal actual fun _startAt(document: DocumentSnapshot) = Query(android.startAt(document.android).native) - internal actual fun _startAt(vararg fieldValues: Any) = Query(android.startAt(*fieldValues).native) + actual fun startAfter(document: NativeDocumentSnapshot) = native.startAfter(document).wrapped + actual fun startAfter(vararg fieldValues: Any) = native.startAfter(*fieldValues).wrapped + actual fun startAt(document: NativeDocumentSnapshot) = native.startAt(document).wrapped + actual fun startAt(vararg fieldValues: Any) = native.startAt(*fieldValues).wrapped - internal actual fun _endBefore(document: DocumentSnapshot) = Query(android.endBefore(document.android).native) - internal actual fun _endBefore(vararg fieldValues: Any) = Query(android.endBefore(*fieldValues).native) - internal actual fun _endAt(document: DocumentSnapshot) = Query(android.endAt(document.android).native) - internal actual fun _endAt(vararg fieldValues: Any) = Query(android.endAt(*fieldValues).native) + actual fun endBefore(document: NativeDocumentSnapshot) = native.endBefore(document).wrapped + actual fun endBefore(vararg fieldValues: Any) = native.endBefore(*fieldValues).wrapped + actual fun endAt(document: NativeDocumentSnapshot) = native.endAt(document).wrapped + actual fun endAt(vararg fieldValues: Any) = native.endAt(*fieldValues).wrapped } +val Query.android get() = native + +internal val AndroidQuery.wrapped get() = NativeQueryWrapper(this) + actual typealias Direction = com.google.firebase.firestore.Query.Direction actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type +actual typealias NativeCollectionReference = com.google.firebase.firestore.CollectionReference + @PublishedApi -internal actual class NativeCollectionReferenceWrapper(override val android: com.google.firebase.firestore.CollectionReference) : NativeQueryWrapper(android) { +internal actual class NativeCollectionReferenceWrapper internal actual constructor(override actual val native: NativeCollectionReference) : NativeQueryWrapper(native) { actual val path: String - get() = android.path + get() = native.path actual val document: NativeDocumentReference - get() = NativeDocumentReference(android.document()) + get() = NativeDocumentReference(native.document()) actual val parent: NativeDocumentReference? - get() = android.parent?.let{ NativeDocumentReference(it) } + get() = native.parent?.let{ NativeDocumentReference(it) } - actual fun document(documentPath: String) = NativeDocumentReference(android.document(documentPath)) + actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - actual suspend fun addEncoded(data: Any) = NativeDocumentReference(android.add(data).await()) + actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(native.add(data.android).await()) } -val CollectionReference.android get() = nativeWrapper.android +val CollectionReference.android get() = native actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException @@ -377,21 +380,24 @@ actual class DocumentChange(val android: com.google.firebase.firestore.DocumentC get() = android.type } +actual typealias NativeDocumentSnapshot = com.google.firebase.firestore.DocumentSnapshot + @PublishedApi -internal actual class NativeDocumentSnapshotWrapper(val android: com.google.firebase.firestore.DocumentSnapshot) { +internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: com.google.firebase.firestore.DocumentSnapshot) { - actual val id get() = android.id - actual val reference get() = NativeDocumentReference(android.reference) + actual val id get() = native.id + actual val reference get() = NativeDocumentReference(native.reference) - actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = android.get(field, serverTimestampBehavior.toAndroid()) - actual fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = android.get(fieldPath.encoded, serverTimestampBehavior.toAndroid()) - actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = android.getData(serverTimestampBehavior.toAndroid()) + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(field, serverTimestampBehavior.toAndroid()) + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(fieldPath, serverTimestampBehavior.toAndroid()) + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = native.getData(serverTimestampBehavior.toAndroid()) - actual fun contains(field: String) = android.contains(field) + actual fun contains(field: String) = native.contains(field) + actual fun contains(fieldPath: EncodedFieldPath) = native.contains(fieldPath) - actual val exists get() = android.exists() + actual val exists get() = native.exists() - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.metadata) + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) fun ServerTimestampBehavior.toAndroid(): com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior = when (this) { ServerTimestampBehavior.ESTIMATE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.ESTIMATE @@ -400,7 +406,7 @@ internal actual class NativeDocumentSnapshotWrapper(val android: com.google.fire } } -val DocumentSnapshot.android get() = nativeWrapper.android +val DocumentSnapshot.android get() = native actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { actual val hasPendingWrites: Boolean get() = android.hasPendingWrites() diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 88915b86e..0aaf75146 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -132,32 +132,60 @@ data class Transaction internal constructor(@PublishedApi internal val nativeWra suspend fun get(documentRef: DocumentReference): DocumentSnapshot = DocumentSnapshot(nativeWrapper.get(documentRef)) } +expect open class NativeQuery + @PublishedApi -internal expect open class NativeQuery +internal expect open class NativeQueryWrapper internal constructor(native: NativeQuery) { + + open val native: NativeQuery -expect open class Query internal constructor(nativeQuery: NativeQuery) { - fun limit(limit: Number): Query + fun limit(limit: Number): NativeQueryWrapper val snapshots: Flow fun snapshots(includeMetadataChanges: Boolean = false): Flow suspend fun get(): QuerySnapshot - internal fun where(filter: Filter): Query + fun where(filter: Filter): NativeQueryWrapper - internal fun _orderBy(field: String, direction: Direction): Query - internal fun _orderBy(field: FieldPath, direction: Direction): Query + fun orderBy(field: String, direction: Direction): NativeQueryWrapper + fun orderBy(field: EncodedFieldPath, direction: Direction): NativeQueryWrapper - internal fun _startAfter(document: DocumentSnapshot): Query - internal fun _startAfter(vararg fieldValues: Any): Query - internal fun _startAt(document: DocumentSnapshot): Query - internal fun _startAt(vararg fieldValues: Any): Query + fun startAfter(document: NativeDocumentSnapshot): NativeQueryWrapper + fun startAfter(vararg fieldValues: Any): NativeQueryWrapper + fun startAt(document: NativeDocumentSnapshot): NativeQueryWrapper + fun startAt(vararg fieldValues: Any): NativeQueryWrapper - internal fun _endBefore(document: DocumentSnapshot): Query - internal fun _endBefore(vararg fieldValues: Any): Query - internal fun _endAt(document: DocumentSnapshot): Query - internal fun _endAt(vararg fieldValues: Any): Query + fun endBefore(document: NativeDocumentSnapshot): NativeQueryWrapper + fun endBefore(vararg fieldValues: Any): NativeQueryWrapper + fun endAt(document: NativeDocumentSnapshot): NativeQueryWrapper + fun endAt(vararg fieldValues: Any): NativeQueryWrapper } -fun Query.where(builder: FilterBuilder.() -> Filter?) = builder(FilterBuilder())?.let { where(it) } ?: this +open class Query internal constructor(internal val nativeQuery: NativeQueryWrapper) { + + constructor(native: NativeQuery) : this(NativeQueryWrapper(native)) + + open val native = nativeQuery.native + + fun limit(limit: Number): Query = Query(nativeQuery.limit(limit)) + val snapshots: Flow = nativeQuery.snapshots + fun snapshots(includeMetadataChanges: Boolean = false): Flow = nativeQuery.snapshots(includeMetadataChanges) + suspend fun get(): QuerySnapshot = nativeQuery.get() + + fun where(builder: FilterBuilder.() -> Filter?) = builder(FilterBuilder())?.let { Query(nativeQuery.where(it)) } ?: this + + fun orderBy(field: String, direction: Direction = Direction.ASCENDING) = Query(nativeQuery.orderBy(field, direction)) + fun orderBy(field: FieldPath, direction: Direction = Direction.ASCENDING) = Query(nativeQuery.orderBy(field.encoded, direction)) + + fun startAfter(document: DocumentSnapshot) = Query(nativeQuery.startAfter(document.native)) + fun startAfter(vararg fieldValues: Any) = Query(nativeQuery.startAfter(*(fieldValues.map { it.safeValue }.toTypedArray()))) + fun startAt(document: DocumentSnapshot) = Query(nativeQuery.startAt(document.native)) + fun startAt(vararg fieldValues: Any) = Query(nativeQuery.startAt(*(fieldValues.map { it.safeValue }.toTypedArray()))) + + fun endBefore(document: DocumentSnapshot) = Query(nativeQuery.endBefore(document.native)) + fun endBefore(vararg fieldValues: Any) = Query(nativeQuery.endBefore(*(fieldValues.map { it.safeValue }.toTypedArray()))) + fun endAt(document: DocumentSnapshot) = Query(nativeQuery.endAt(document.native)) + fun endAt(vararg fieldValues: Any) = Query(nativeQuery.endAt(*(fieldValues.map { it.safeValue }.toTypedArray()))) +} @Deprecated("Deprecated in favor of using a [FilterBuilder]", replaceWith = ReplaceWith("where { field equalTo equalTo }", "dev.gitlive.firebase.firestore")) fun Query.where(field: String, equalTo: Any?) = where { @@ -211,19 +239,6 @@ fun Query.where(path: FieldPath, inArray: List? = null, arrayContainsAny: L ) } -fun Query.orderBy(field: String, direction: Direction = Direction.ASCENDING) = _orderBy(field, direction) -fun Query.orderBy(field: FieldPath, direction: Direction = Direction.ASCENDING) = _orderBy(field, direction) - -fun Query.startAfter(document: DocumentSnapshot) = _startAfter(document) -fun Query.startAfter(vararg fieldValues: Any) = _startAfter(*(fieldValues.mapNotNull { it.safeValue }.toTypedArray())) -fun Query.startAt(document: DocumentSnapshot) = _startAt(document) -fun Query.startAt(vararg fieldValues: Any) = _startAt(*(fieldValues.mapNotNull { it.safeValue }.toTypedArray())) - -fun Query.endBefore(document: DocumentSnapshot) = _endBefore(document) -fun Query.endBefore(vararg fieldValues: Any) = _endBefore(*(fieldValues.mapNotNull { it.safeValue }.toTypedArray())) -fun Query.endAt(document: DocumentSnapshot) = _endAt(document) -fun Query.endAt(vararg fieldValues: Any) = _endAt(*(fieldValues.mapNotNull { it.safeValue }.toTypedArray())) - internal val Any.safeValue: Any get() = when (this) { is Timestamp -> nativeValue is GeoPoint -> nativeValue @@ -236,7 +251,8 @@ internal val Any.safeValue: Any get() = when (this) { expect class NativeWriteBatch @PublishedApi -internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch){ +internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch) { + val native: NativeWriteBatch fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeWriteBatchWrapper fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper @@ -245,10 +261,12 @@ internal expect class NativeWriteBatchWrapper internal constructor(native: Nativ suspend fun commit() } -data class WriteBatch internal constructor(@PublishedApi internal val native: NativeWriteBatchWrapper) { +data class WriteBatch internal constructor(@PublishedApi internal val nativeWrapper: NativeWriteBatchWrapper) { constructor(native: NativeWriteBatch) : this(NativeWriteBatchWrapper(native)) + val native = nativeWrapper.native + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults }")) inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean = false) = set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults @@ -292,7 +310,7 @@ data class WriteBatch internal constructor(@PublishedApi internal val native: Na setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) @PublishedApi - internal fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions) = WriteBatch(native.setEncoded(documentRef, encodedData, setOptions)) + internal fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions) = WriteBatch(nativeWrapper.setEncoded(documentRef, encodedData, setOptions)) @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { this.encodeDefaults = encodeDefaults }")) inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean) = update(documentRef, data) { @@ -314,16 +332,16 @@ data class WriteBatch internal constructor(@PublishedApi internal val native: Na inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) @PublishedApi - internal fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = WriteBatch(native.updateEncoded(documentRef, encodedData)) + internal fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = WriteBatch(nativeWrapper.updateEncoded(documentRef, encodedData)) @PublishedApi - internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(native.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) + internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(nativeWrapper.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) @PublishedApi - internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(native.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) + internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>) = WriteBatch(nativeWrapper.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) - fun delete(documentRef: DocumentReference): WriteBatch = WriteBatch(native.delete(documentRef)) - suspend fun commit() = native.commit() + fun delete(documentRef: DocumentReference): WriteBatch = WriteBatch(nativeWrapper.delete(documentRef)) + suspend fun commit() = nativeWrapper.commit() } /** A class representing a platform specific Firebase DocumentReference. */ @@ -427,12 +445,12 @@ data class DocumentReference internal constructor(@PublishedApi internal val nat suspend fun delete() = native.delete() } -expect class NativeCollectionReference +expect class NativeCollectionReference : NativeQuery @PublishedApi -internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : NativeQuery { +internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : NativeQueryWrapper { - val native: NativeCollectionReference + override val native: NativeCollectionReference val path: String val document: NativeDocumentReference @@ -446,7 +464,7 @@ data class CollectionReference internal constructor(@PublishedApi internal val n constructor(native: NativeCollectionReference) : this(NativeCollectionReferenceWrapper(native)) - val native = nativeWrapper.native + override val native = nativeWrapper.native val path: String get() = nativeWrapper.path val document: DocumentReference get() = DocumentReference(nativeWrapper.document) @@ -535,10 +553,10 @@ internal expect class NativeDocumentSnapshotWrapper internal constructor(native: val metadata: SnapshotMetadata fun contains(field: String): Boolean - fun contains(fieldPath: FieldPath): Boolean + fun contains(fieldPath: EncodedFieldPath): Boolean fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? - fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? } @@ -554,7 +572,7 @@ data class DocumentSnapshot internal constructor(@PublishedApi internal val nati val metadata: SnapshotMetadata get() = nativeWrapper.metadata fun contains(field: String): Boolean = nativeWrapper.contains(field) - fun contains(fieldPath: FieldPath): Boolean = nativeWrapper.contains(fieldPath) + fun contains(fieldPath: FieldPath): Boolean = nativeWrapper.contains(fieldPath.encoded) inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(value = getEncoded(field, serverTimestampBehavior), buildSettings) inline fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(field, serverTimestampBehavior), buildSettings) @@ -566,7 +584,7 @@ data class DocumentSnapshot internal constructor(@PublishedApi internal val nati inline fun get(fieldPath: FieldPath, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(fieldPath, serverTimestampBehavior), buildSettings) @PublishedApi - internal fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.getEncoded(fieldPath, serverTimestampBehavior) + internal fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.getEncoded(fieldPath.encoded, serverTimestampBehavior) inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(encodedData(serverTimestampBehavior), buildSettings) diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 2689c0950..ec87011db 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -26,7 +26,7 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReferenceWrapper(ios.collectionWithPath(collectionPath))) - actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId).native) + actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId).wrapped) actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(ios.documentWithPath(documentPath))) @@ -70,7 +70,7 @@ actual class FirebaseFirestore(val ios: FIRFirestore) { actual typealias NativeWriteBatch = FIRWriteBatch @PublishedApi -internal actual class NativeWriteBatchWrapper actual constructor(val native: FIRWriteBatch) { +internal actual class NativeWriteBatchWrapper actual constructor(actual val native: NativeWriteBatch) { actual fun setEncoded( documentRef: DocumentReference, @@ -107,7 +107,7 @@ internal actual class NativeWriteBatchWrapper actual constructor(val native: FIR actual suspend fun commit() = await { native.commitWithCompletion(it) } } -val WriteBatch.ios get() = native.native +val WriteBatch.ios get() = native actual typealias NativeTransaction = FIRTransaction @@ -151,7 +151,7 @@ internal actual class NativeTransactionWrapper actual constructor(actual val nat } -val Transaction.ios get() = nativeWrapper.native +val Transaction.ios get() = native /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = FIRDocumentReference @@ -223,20 +223,16 @@ internal actual class NativeDocumentReference actual constructor(actual val nati val DocumentReference.ios get() = native.ios -@PublishedApi -internal actual open class NativeQuery(open val ios: FIRQuery) -internal val FIRQuery.native get() = NativeQuery(this) - -actual open class Query internal actual constructor(nativeQuery: NativeQuery) { - - open val ios: FIRQuery = nativeQuery.ios +actual typealias NativeQuery = FIRQuery - actual suspend fun get() = QuerySnapshot(awaitResult { ios.getDocumentsWithCompletion(it) }) +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { + actual suspend fun get() = QuerySnapshot(awaitResult { native.getDocumentsWithCompletion(it) }) - actual fun limit(limit: Number) = Query(ios.queryLimitedTo(limit.toLong()).native) + actual fun limit(limit: Number) = native.queryLimitedTo(limit.toLong()).wrapped actual val snapshots get() = callbackFlow { - val listener = ios.addSnapshotListener { snapshot, error -> + val listener = native.addSnapshotListener { snapshot, error -> snapshot?.let { trySend(QuerySnapshot(snapshot)) } error?.let { close(error.toException()) } } @@ -244,16 +240,14 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> + val listener = native.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> snapshot?.let { trySend(QuerySnapshot(snapshot)) } error?.let { close(error.toException()) } } awaitClose { listener.remove() } } - internal actual fun where(filter: Filter): Query = Query( - ios.queryWhereFilter(filter.toFIRFilter()).native - ) + actual fun where(filter: Filter) = native.queryWhereFilter(filter.toFIRFilter()).wrapped private fun Filter.toFIRFilter(): FIRFilter = when (this) { is Filter.And -> FIRFilter.andFilterWithFilters(filters.map { it.toFIRFilter() }) @@ -284,41 +278,42 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { } } - internal actual fun _orderBy(field: String, direction: Direction) = Query(ios.queryOrderedByField(field, direction == Direction.DESCENDING).native) - internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(ios.queryOrderedByFieldPath(field.ios, direction == Direction.DESCENDING).native) + actual fun orderBy(field: String, direction: Direction) = native.queryOrderedByField(field, direction == Direction.DESCENDING).wrapped + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.queryOrderedByFieldPath(field, direction == Direction.DESCENDING).wrapped - internal actual fun _startAfter(document: DocumentSnapshot) = Query(ios.queryStartingAfterDocument(document.ios).native) - internal actual fun _startAfter(vararg fieldValues: Any) = Query(ios.queryStartingAfterValues(fieldValues.asList()).native) - internal actual fun _startAt(document: DocumentSnapshot) = Query(ios.queryStartingAtDocument(document.ios).native) - internal actual fun _startAt(vararg fieldValues: Any) = Query(ios.queryStartingAtValues(fieldValues.asList()).native) - - internal actual fun _endBefore(document: DocumentSnapshot) = Query(ios.queryEndingBeforeDocument(document.ios).native) - internal actual fun _endBefore(vararg fieldValues: Any) = Query(ios.queryEndingBeforeValues(fieldValues.asList()).native) - internal actual fun _endAt(document: DocumentSnapshot) = Query(ios.queryEndingAtDocument(document.ios).native) - internal actual fun _endAt(vararg fieldValues: Any) = Query(ios.queryEndingAtValues(fieldValues.asList()).native) + actual fun startAfter(document: NativeDocumentSnapshot) = native.queryStartingAfterDocument(document).wrapped + actual fun startAfter(vararg fieldValues: Any) = native.queryStartingAfterValues(fieldValues.asList()).wrapped + actual fun startAt(document: NativeDocumentSnapshot) = native.queryStartingAtDocument(document).wrapped + actual fun startAt(vararg fieldValues: Any) = native.queryStartingAtValues(fieldValues.asList()).wrapped + actual fun endBefore(document: NativeDocumentSnapshot) = native.queryEndingBeforeDocument(document).wrapped + actual fun endBefore(vararg fieldValues: Any) = native.queryEndingBeforeValues(fieldValues.asList()).wrapped + actual fun endAt(document: NativeDocumentSnapshot) = native.queryEndingAtDocument(document).wrapped + actual fun endAt(vararg fieldValues: Any) = native.queryEndingAtValues(fieldValues.asList()).wrapped } +val Query.ios get() = native + +internal val FIRQuery.wrapped get() = NativeQueryWrapper(this) + actual typealias NativeCollectionReference = FIRCollectionReference @PublishedApi -internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual val native: NativeCollectionReference) : NativeQuery(native) { - - override val ios: FIRCollectionReference = native +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { actual val path: String - get() = ios.path + get() = native.path - actual val document get() = NativeDocumentReference(ios.documentWithAutoID()) + actual val document get() = NativeDocumentReference(native.documentWithAutoID()) - actual val parent get() = ios.parent?.let{ NativeDocumentReference(it) } + actual val parent get() = native.parent?.let{ NativeDocumentReference(it) } - actual fun document(documentPath: String) = NativeDocumentReference(ios.documentWithPath(documentPath)) + actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { ios.addDocumentWithData(data.ios, it) }) + actual suspend fun addEncoded(data: EncodedObject) = NativeDocumentReference(await { native.addDocumentWithData(data.ios, it) }) } -val CollectionReference.ios get() = nativeWrapper.ios +val CollectionReference.ios get() = native actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) @@ -411,8 +406,8 @@ internal actual class NativeDocumentSnapshotWrapper actual constructor(actual va native.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } // Despite its name implying otherwise, valueForField accepts both a String representation of a Field and a FIRFieldPath - actual fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = - native.valueForField(fieldPath.ios, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.valueForField(fieldPath, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = native.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) @@ -421,7 +416,7 @@ internal actual class NativeDocumentSnapshotWrapper actual constructor(actual va } actual fun contains(field: String) = native.valueForField(field) != null - actual fun contains(fieldPath: FieldPath) = native.valueForField(fieldPath.ios) != null + actual fun contains(fieldPath: EncodedFieldPath) = native.valueForField(fieldPath) != null actual val exists get() = native.exists @@ -434,7 +429,7 @@ internal actual class NativeDocumentSnapshotWrapper actual constructor(actual va } } -val DocumentSnapshot.ios get() = nativeWrapper.native +val DocumentSnapshot.ios get() = native actual class SnapshotMetadata(val ios: FIRSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = ios.pendingWrites diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index b2dfdb0e3..c6b3effff 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -74,7 +74,7 @@ actual class FirebaseFirestore(jsFirestore: Firestore) { actual fun collection(collectionPath: String) = rethrow { CollectionReference(NativeCollectionReferenceWrapper(jsCollection(js, collectionPath))) } - actual fun collectionGroup(collectionId: String) = rethrow { Query(jsCollectionGroup(js, collectionId)) } + actual fun collectionGroup(collectionId: String) = rethrow { Query(jsCollectionGroup(js, collectionId).wrapped) } actual fun document(documentPath: String) = rethrow { DocumentReference(NativeDocumentReference(doc(js, documentPath))) } @@ -118,16 +118,22 @@ internal val SetOptions.js: Json get() = when (this) { is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) } +actual data class NativeWriteBatch(val js: JsWriteBatch) + @PublishedApi -internal actual class NativeWriteBatchWrapper(val js: JsWriteBatch) { +internal actual class NativeWriteBatchWrapper actual internal constructor(actual val native: NativeWriteBatch) { + + constructor(js: JsWriteBatch) : this(NativeWriteBatch(js)) + + val js = native.js actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions - ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } + ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.json, setOptions.js) }.let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.json) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -157,19 +163,25 @@ internal actual class NativeWriteBatchWrapper(val js: JsWriteBatch) { val WriteBatch.js get() = native.js +actual data class NativeTransaction(val js: JsTransaction) + @PublishedApi -internal actual class NativeTransactionWrapper(val js: JsTransaction) { +internal actual class NativeTransactionWrapper actual internal constructor(actual val native: NativeTransaction) { + + constructor(js: JsTransaction) : this(NativeTransaction(js)) + + val js = native.js actual fun setEncoded( documentRef: DocumentReference, - encodedData: Any, + encodedData: EncodedObject, setOptions: SetOptions ): NativeTransactionWrapper = rethrow { - js.set(documentRef.js, encodedData, setOptions.js) + js.set(documentRef.js, encodedData.json, setOptions.js) } .let { this } - actual fun updateEncoded(documentRef: DocumentReference, encodedData: Any): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData) } + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.json) } .let { this } actual fun updateEncodedFieldsAndValues( @@ -198,7 +210,7 @@ internal actual class NativeTransactionWrapper(val js: JsTransaction) { rethrow { NativeDocumentSnapshotWrapper(js.get(documentRef.js).await()) } } -val Transaction.js get() = nativeWrapper.js +val Transaction.js get() = native.js /** A class representing a platform specific Firebase DocumentReference. */ actual typealias NativeDocumentReferenceType = JsDocumentReference @@ -233,10 +245,10 @@ internal actual class NativeDocumentReference actual constructor(actual val nati } actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { - setDoc(js, encodedData, setOptions.js).await() + setDoc(js, encodedData.json, setOptions.js).await() } - actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData).await() } + actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { jsUpdate(js, encodedData.json).await() } actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { rethrow { @@ -267,22 +279,21 @@ internal actual class NativeDocumentReference actual constructor(actual val nati val DocumentReference.js get() = native.js -@PublishedApi -internal actual open class NativeQueryWrapper(open val js: JsQuery) +actual open class NativeQuery(open val js: JsQuery) +internal val JsQuery.wrapped get() = NativeQueryWrapper(this) -actual open class Query internal actual constructor(nativeWrapper: NativeQueryWrapper) { +@PublishedApi +internal actual open class NativeQueryWrapper actual internal constructor(actual open val native: NativeQuery) { - constructor(js: JsQuery) : this(NativeQueryWrapper(js)) + constructor(js: JsQuery) : this(NativeQuery(js)) - open val js: JsQuery = nativeWrapper.js + open val js: JsQuery get() = native.js actual suspend fun get() = rethrow { QuerySnapshot(getDocs(js).await()) } - actual fun limit(limit: Number) = Query(query(js, jsLimit(limit))) + actual fun limit(limit: Number) = query(js, jsLimit(limit)).wrapped - internal actual fun where(filter: Filter): Query = Query( - query(js, filter.toQueryConstraint()) - ) + actual fun where(filter: Filter) = query(js, filter.toQueryConstraint()).wrapped private fun Filter.toQueryConstraint(): QueryConstraint = when (this) { is Filter.And -> and(*filters.map { it.toQueryConstraint() }.toTypedArray()) @@ -318,29 +329,29 @@ actual open class Query internal actual constructor(nativeWrapper: NativeQueryWr is WhereConstraint.NotInArray -> "not-in" } - internal actual fun _orderBy(field: String, direction: Direction) = rethrow { - Query(query(js, orderBy(field, direction.jsString))) + actual fun orderBy(field: String, direction: Direction) = rethrow { + query(js, orderBy(field, direction.jsString)).wrapped } - internal actual fun _orderBy(field: FieldPath, direction: Direction) = rethrow { - Query(query(js, orderBy(field.js, direction.jsString))) + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = rethrow { + query(js, orderBy(field, direction.jsString)).wrapped } - internal actual fun _startAfter(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAfter(document.js))) } + actual fun startAfter(document: NativeDocumentSnapshot) = rethrow { query(js, jsStartAfter(document.js)).wrapped } - internal actual fun _startAfter(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAfter(*fieldValues))) } + actual fun startAfter(vararg fieldValues: Any) = rethrow { query(js, jsStartAfter(*fieldValues)).wrapped } - internal actual fun _startAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAt(document.js))) } + actual fun startAt(document: NativeDocumentSnapshot) = rethrow { query(js, jsStartAt(document.js)).wrapped } - internal actual fun _startAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAt(*fieldValues))) } + actual fun startAt(vararg fieldValues: Any) = rethrow { query(js, jsStartAt(*fieldValues)).wrapped } - internal actual fun _endBefore(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndBefore(document.js))) } + actual fun endBefore(document: NativeDocumentSnapshot) = rethrow { query(js, jsEndBefore(document.js)).wrapped } - internal actual fun _endBefore(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndBefore(*fieldValues))) } + actual fun endBefore(vararg fieldValues: Any) = rethrow { query(js, jsEndBefore(*fieldValues)).wrapped } - internal actual fun _endAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndAt(document.js))) } + actual fun endAt(document: NativeDocumentSnapshot) = rethrow { query(js, jsEndAt(document.js)).wrapped } - internal actual fun _endAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndAt(*fieldValues))) } + actual fun endAt(vararg fieldValues: Any) = rethrow { query(js, jsEndAt(*fieldValues)).wrapped } actual val snapshots get() = callbackFlow { val unsubscribe = rethrow { @@ -366,8 +377,16 @@ actual open class Query internal actual constructor(nativeWrapper: NativeQueryWr } } +val Query.js get() = native.js + +actual data class NativeCollectionReference(override val js: JsCollectionReference) : NativeQuery(js) + @PublishedApi -internal actual class NativeCollectionReferenceWrapper(override val js: JsCollectionReference) : NativeQueryWrapper(js) { +internal actual class NativeCollectionReferenceWrapper actual internal constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + constructor(js: JsCollectionReference) : this(NativeCollectionReference(js)) + + override val js: JsCollectionReference = native.js actual val path: String get() = rethrow { js.path } @@ -378,12 +397,12 @@ internal actual class NativeCollectionReferenceWrapper(override val js: JsCollec actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } - actual suspend fun addEncoded(data: Any) = rethrow { - NativeDocumentReference(addDoc(js, data).await()) + actual suspend fun addEncoded(data: EncodedObject) = rethrow { + NativeDocumentReference(addDoc(js, data.json).await()) } } -val CollectionReference.js get() = nativeWrapper.js +val CollectionReference.js get() = native.js actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) @@ -409,8 +428,14 @@ actual class DocumentChange(val js: JsDocumentChange) { get() = ChangeType.values().first { it.jsString == js.type } } +actual data class NativeDocumentSnapshot(val js: JsDocumentSnapshot) + @PublishedApi -internal actual class NativeDocumentSnapshotWrapper(val js: JsDocumentSnapshot) { +internal actual class NativeDocumentSnapshotWrapper actual internal constructor(actual val native: NativeDocumentSnapshot) { + + constructor(js: JsDocumentSnapshot) : this(NativeDocumentSnapshot(js)) + + val js: JsDocumentSnapshot = native.js actual val id get() = rethrow { js.id } actual val reference get() = rethrow { NativeDocumentReference(js.ref) } @@ -419,11 +444,16 @@ internal actual class NativeDocumentSnapshotWrapper(val js: JsDocumentSnapshot) js.get(field, getTimestampsOptions(serverTimestampBehavior)) } + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.get(fieldPath, getTimestampsOptions(serverTimestampBehavior)) + } + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { js.data(getTimestampsOptions(serverTimestampBehavior)) } actual fun contains(field: String) = rethrow { js.get(field) != undefined } + actual fun contains(fieldPath: EncodedFieldPath) = rethrow { js.get(fieldPath) != undefined } actual val exists get() = rethrow { js.exists() } actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) @@ -431,7 +461,7 @@ internal actual class NativeDocumentSnapshotWrapper(val js: JsDocumentSnapshot) json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) } -val DocumentSnapshot.js get() = nativeWrapper.js +val DocumentSnapshot.js get() = native.js actual class SnapshotMetadata(val js: JsSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = js.hasPendingWrites