jsMain.dev.gitlive.firebase.database.database.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of firebase-database-js Show documentation
Show all versions of firebase-database-js Show documentation
The Firebase Kotlin SDK is a Kotlin-first SDK for Firebase. It's API is similar to the Firebase Android SDK Kotlin Extensions but also supports multiplatform projects, enabling you to use Firebase directly from your common source targeting iOS, Android or JS.
/*
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
*/
package dev.gitlive.firebase.database
import dev.gitlive.firebase.*
import dev.gitlive.firebase.FirebaseApp
import dev.gitlive.firebase.database.externals.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.produceIn
import kotlinx.coroutines.selects.select
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlin.js.Promise
import kotlin.js.json
import dev.gitlive.firebase.database.externals.DataSnapshot as JsDataSnapshot
import dev.gitlive.firebase.database.externals.DatabaseReference as JsDatabaseReference
import dev.gitlive.firebase.database.externals.OnDisconnect as JsOnDisconnect
import dev.gitlive.firebase.database.externals.Query as JsQuery
import dev.gitlive.firebase.database.externals.endAt as jsEndAt
import dev.gitlive.firebase.database.externals.equalTo as jsEqualTo
import dev.gitlive.firebase.database.externals.limitToFirst as jsLimitToFirst
import dev.gitlive.firebase.database.externals.limitToLast as jsLimitToLast
import dev.gitlive.firebase.database.externals.orderByChild as jsOrderByChild
import dev.gitlive.firebase.database.externals.orderByKey as jsOrderByKey
import dev.gitlive.firebase.database.externals.orderByValue as jsOrderByValue
import dev.gitlive.firebase.database.externals.startAt as jsStartAt
actual val Firebase.database
get() = rethrow { FirebaseDatabase(getDatabase()) }
actual fun Firebase.database(app: FirebaseApp) =
rethrow { FirebaseDatabase(getDatabase(app = app.js)) }
actual fun Firebase.database(url: String) =
rethrow { FirebaseDatabase(getDatabase(url = url)) }
actual fun Firebase.database(app: FirebaseApp, url: String) =
rethrow { FirebaseDatabase(getDatabase(app = app.js, url = url)) }
actual class FirebaseDatabase internal constructor(val js: Database) {
actual fun reference(path: String) = rethrow { DatabaseReference(ref(js, path), js) }
actual fun reference() = rethrow { DatabaseReference(ref(js), js) }
actual fun setPersistenceEnabled(enabled: Boolean) {}
actual fun setLoggingEnabled(enabled: Boolean) = rethrow { enableLogging(enabled) }
actual fun useEmulator(host: String, port: Int) = rethrow { connectDatabaseEmulator(js, host, port) }
}
actual open class Query internal constructor(
open val js: JsQuery,
val database: Database
) {
actual fun orderByKey() = Query(query(js, jsOrderByKey()), database)
actual fun orderByValue() = Query(query(js, jsOrderByValue()), database)
actual fun orderByChild(path: String) = Query(query(js, jsOrderByChild(path)), database)
actual val valueEvents
get() = callbackFlow {
val unsubscribe = rethrow {
onValue(
query = js,
callback = { trySend(DataSnapshot(it, database)) },
cancelCallback = { close(DatabaseException(it)).run { } }
)
}
awaitClose { rethrow { unsubscribe() } }
}
actual fun childEvents(vararg types: ChildEvent.Type) = callbackFlow {
val unsubscribes = rethrow {
types.map { type ->
val callback: ChangeSnapshotCallback = { snapshot, previousChildName ->
trySend(
ChildEvent(
DataSnapshot(snapshot, database),
type,
previousChildName
)
)
}
val cancelCallback: CancelCallback = {
close(DatabaseException(it)).run { }
}
when (type) {
ChildEvent.Type.ADDED -> onChildAdded(js, callback, cancelCallback)
ChildEvent.Type.CHANGED -> onChildChanged(js, callback, cancelCallback)
ChildEvent.Type.MOVED -> onChildMoved(js, callback, cancelCallback)
ChildEvent.Type.REMOVED -> onChildRemoved(js, callback, cancelCallback)
}
}
}
awaitClose { rethrow { unsubscribes.forEach { it.invoke() } } }
}
actual fun startAt(value: String, key: String?) = Query(query(js, jsStartAt(value, key ?: undefined)), database)
actual fun startAt(value: Double, key: String?) = Query(query(js, jsStartAt(value, key ?: undefined)), database)
actual fun startAt(value: Boolean, key: String?) = Query(query(js, jsStartAt(value, key ?: undefined)), database)
actual fun endAt(value: String, key: String?) = Query(query(js, jsEndAt(value, key ?: undefined)), database)
actual fun endAt(value: Double, key: String?) = Query(query(js, jsEndAt(value, key ?: undefined)), database)
actual fun endAt(value: Boolean, key: String?) = Query(query(js, jsEndAt(value, key ?: undefined)), database)
actual fun limitToFirst(limit: Int) = Query(query(js, jsLimitToFirst(limit)), database)
actual fun limitToLast(limit: Int) = Query(query(js, jsLimitToLast(limit)), database)
actual fun equalTo(value: String, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database)
actual fun equalTo(value: Double, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database)
actual fun equalTo(value: Boolean, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database)
override fun toString() = js.toString()
}
actual class DatabaseReference internal constructor(
override val js: JsDatabaseReference,
database: Database
) : Query(js, database) {
actual val key get() = rethrow { js.key }
actual fun push() = rethrow { DatabaseReference(push(js), database) }
actual fun child(path: String) = rethrow { DatabaseReference(child(js, path), database) }
actual fun onDisconnect() = rethrow { OnDisconnect(onDisconnect(js), database) }
actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) =
rethrow { update(js, encode(update, encodeDefaults) ?: json()).awaitWhileOnline(database) }
actual suspend fun removeValue() = rethrow { remove(js).awaitWhileOnline(database) }
actual suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = rethrow {
set(js, encode(value, encodeDefaults)).awaitWhileOnline(database)
}
actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) =
rethrow { set(js, encode(strategy, value, encodeDefaults)).awaitWhileOnline(database) }
actual suspend fun runTransaction(strategy: KSerializer, transactionUpdate: (currentData: T) -> T): DataSnapshot =
rethrow {
val result = runTransaction(
js,
transactionUpdate,
).awaitWhileOnline(database)
DataSnapshot(result.snapshot, database)
}
}
actual class DataSnapshot internal constructor(
val js: JsDataSnapshot,
val database: Database
) {
actual val value get(): Any? {
check(!hasChildren) { "DataSnapshot.value can only be used for primitive values (snapshots without children)" }
return js.`val`()
}
actual inline fun value() =
rethrow { decode(value = js.`val`()) }
actual fun value(strategy: DeserializationStrategy) =
rethrow { decode(strategy, js.`val`()) }
actual val exists get() = rethrow { js.exists() }
actual val key get() = rethrow { js.key }
actual fun child(path: String) = DataSnapshot(js.child(path), database)
actual val hasChildren get() = js.hasChildren()
actual val children: Iterable = rethrow {
ArrayList(js.size).also {
js.forEach { snapshot -> it.add(DataSnapshot(snapshot, database)); false /* don't cancel enumeration */ }
}
}
actual val ref: DatabaseReference
get() = DatabaseReference(js.ref, database)
}
actual class OnDisconnect internal constructor(
val js: JsOnDisconnect,
val database: Database
) {
actual suspend fun removeValue() = rethrow { js.remove().awaitWhileOnline(database) }
actual suspend fun cancel() = rethrow { js.cancel().awaitWhileOnline(database) }
actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) =
rethrow { js.update(encode(update, encodeDefaults) ?: json()).awaitWhileOnline(database) }
actual suspend inline fun setValue(value: T, encodeDefaults: Boolean) =
rethrow { js.set(encode(value, encodeDefaults)).awaitWhileOnline(database) }
actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) =
rethrow { js.set(encode(strategy, value, encodeDefaults)).awaitWhileOnline(database) }
}
actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) {
constructor(error: dynamic) : this("${error.code ?: "UNKNOWN"}: ${error.message}", error.unsafeCast())
}
inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.database.rethrow { function() }
inline fun rethrow(function: () -> R): R {
try {
return function()
} catch (e: Exception) {
throw e
} catch (e: dynamic) {
throw DatabaseException(e)
}
}
suspend fun Promise.awaitWhileOnline(database: Database): T = coroutineScope {
val notConnected = FirebaseDatabase(database)
.reference(".info/connected")
.valueEvents
.filter { !it.value() }
.produceIn(this)
select {
[email protected]().onAwait { it.also { notConnected.cancel() } }
notConnected.onReceive { throw DatabaseException("Database not connected", null) }
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy