All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jsMain.dev.gitlive.firebase.database.database.kt Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 2.1.0
Show newest version
/*
 * 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