/*
* 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.DecodeSettings
import dev.gitlive.firebase.EncodeDecodeSettingsBuilder
import dev.gitlive.firebase.EncodeSettings
import dev.gitlive.firebase.internal.EncodedObject
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.FirebaseApp
import dev.gitlive.firebase.database.ChildEvent.Type.ADDED
import dev.gitlive.firebase.database.ChildEvent.Type.CHANGED
import dev.gitlive.firebase.database.ChildEvent.Type.MOVED
import dev.gitlive.firebase.database.ChildEvent.Type.REMOVED
import dev.gitlive.firebase.internal.encode
import dev.gitlive.firebase.internal.encodeAsObject
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
/** Returns the [FirebaseDatabase] instance of the default [FirebaseApp]. */
public expect val Firebase.database: FirebaseDatabase
/** Returns the [FirebaseDatabase] instance for the specified [url]. */
public expect fun Firebase.database(url: String): FirebaseDatabase
/** Returns the [FirebaseDatabase] instance of the given [FirebaseApp]. */
public expect fun Firebase.database(app: FirebaseApp): FirebaseDatabase
/** Returns the [FirebaseDatabase] instance of the given [FirebaseApp] and [url]. */
public expect fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase
/**
* The entry point for accessing a Firebase Database. You can get an instance by calling [Firebase.database]. To access a location in the database and read or write data, use [FirebaseDatabase.reference].
*/
public expect class FirebaseDatabase {
/**
* Gets a DatabaseReference for the provided path.
*
* @param path Path to a location in your FirebaseDatabase.
* @return A DatabaseReference pointing to the specified path.
*/
public fun reference(path: String): DatabaseReference
/**
* Gets a DatabaseReference for the database root node.
*
* @return A DatabaseReference pointing to the root node.
*/
public fun reference(): DatabaseReference
public fun setLoggingEnabled(enabled: Boolean)
/**
* The Firebase Database client will cache synchronized data and keep track of all writes you've
* initiated while your application is running. It seamlessly handles intermittent network
* connections and re-sends write operations when the network connection is restored.
*
* However by default your write operations and cached data are only stored in-memory and will
* be lost when your app restarts. By setting this value to `true`, the data will be persisted to
* on-device (disk) storage and will thus be available again when the app is restarted (even when
* there is no network connectivity at that time). Note that this method must be called before
* creating your first Database reference and only needs to be called once per application.
*
* @param enabled Set to true to enable disk persistence, set to false to disable it.
*/
public fun setPersistenceEnabled(enabled: Boolean)
/**
* By default Firebase Database will use up to 10MB of disk space to cache data. If the cache
* grows beyond this size, Firebase Database will start removing data that hasn't been recently
* used. If you find that your application caches too little or too much data, call this method to
* change the cache size. This method must be called before creating your first Database reference
* and only needs to be called once per application.
*
* Note that the specified cache size is only an approximation and the size on disk may
* temporarily exceed it at times. Cache sizes smaller than 1 MB or greater than 100 MB are not
* supported.
*
* @param cacheSizeInBytes The new size of the cache in bytes.
*/
public fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long)
/**
* Modifies this FirebaseDatabase instance to communicate with the Realtime Database emulator.
*
*
Note: Call this method before using the instance to do any database operations.
*
* @param host the emulator host (for example, 10.0.2.2)
* @param port the emulator port (for example, 9000)
*/
public fun useEmulator(host: String, port: Int)
/**
* Shuts down our connection to the Firebase Database backend until [goOnline] is called.
*/
public fun goOffline()
/**
* Resumes our connection to the Firebase Database backend after a previous [goOffline].
* call.
*/
public fun goOnline()
}
/**
* Used to emit events about changes in the child locations of a given [Query] when using the
* [childEvents] Flow.
*/
public data class ChildEvent internal constructor(
val snapshot: DataSnapshot,
val type: Type,
val previousChildName: String?,
) {
public enum class Type {
/**
* Emitted when a new child is added to the location.
*
* @param snapshot An immutable snapshot of the data at the new child location
* @param previousChildName The key name of sibling location ordered before the new child. This
* ```
* will be null for the first child node of a location.
* ```
*/
ADDED,
/**
* Emitted when the data at a child location has changed.
*
* @param snapshot An immutable snapshot of the data at the new data at the child location
* @param previousChildName The key name of sibling location ordered before the child. This will
* ```
* be null for the first child node of a location.
* ```
*/
CHANGED,
/**
* Emitted when a child location's priority changes.
*
* @param snapshot An immutable snapshot of the data at the location that moved.
* @param previousChildName The key name of the sibling location ordered before the child
* ```
* location. This will be null if this location is ordered first.
* ```
*/
MOVED,
/**
* Emitted when a child is removed from the location.
*
* @param snapshot An immutable snapshot of the data at the child that was removed.
*/
REMOVED,
}
}
internal expect open class NativeQuery
/**
* The Query class (and its subclass, [DatabaseReference]) are used for reading data.
* Listeners are attached, and they will be triggered when the corresponding data changes.
*
* Instances of Query are obtained by calling [startAt], [endAt], or [limit] on a [DatabaseReference].
*/
public expect open class Query internal constructor(nativeQuery: NativeQuery) {
public val valueEvents: Flow
public fun childEvents(vararg types: ChildEvent.Type = arrayOf(ADDED, CHANGED, MOVED, REMOVED)): Flow
/**
* Creates a query in which child nodes are ordered by their keys.
*
* @return A query with the new constraint
*/
public fun orderByKey(): Query
/**
* Creates a query in which nodes are ordered by their value
*
* @return A query with the new constraint
*/
public fun orderByValue(): Query
/**
* Creates a query in which child nodes are ordered by the values of the specified path.
*
* @param path The path to the child node to use for sorting
* @return A query with the new constraint
*/
public fun orderByChild(path: String): Query
/**
* Creates a query constrained to only return child nodes with a value greater than or equal to
* the given value, using the given `orderBy` directive or priority as default.
*
* @param value The value to start at, inclusive
* @return A query with the new constraint
*/
public fun startAt(value: String, key: String? = null): Query
/**
* Creates a query constrained to only return child nodes with a value greater than or equal to
* the given value, using the given `orderBy` directive or priority as default.
*
* @param value The value to start at, inclusive
* @return A query with the new constraint
*/
public fun startAt(value: Double, key: String? = null): Query
/**
* Creates a query constrained to only return child nodes with a value greater than or equal to
* the given value, using the given `orderBy` directive or priority as default.
*
* @param value The value to start at, inclusive
* @return A query with the new constraint
*/
public fun startAt(value: Boolean, key: String? = null): Query
/**
* Creates a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given `orderBy` directive or priority as default.
*
* @param value The value to end at, inclusive
* @return A query with the new constraint
*/
public fun endAt(value: String, key: String? = null): Query
/**
* Creates a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given `orderBy` directive or priority as default.
*
* @param value The value to end at, inclusive
* @return A query with the new constraint
*/
public fun endAt(value: Double, key: String? = null): Query
/**
* Creates a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given `orderBy` directive or priority as default.
*
* @param value The value to end at, inclusive
* @return A query with the new constraint
*/
public fun endAt(value: Boolean, key: String? = null): Query
/**
* Creates a query with limit and anchor it to the start of the window.
*
* @param limit The maximum number of child nodes to return
* @return A query with the new constraint
*/
public fun limitToFirst(limit: Int): Query
/**
* Creates a query with limit and anchor it to the end of the window.
*
* @param limit The maximum number of child nodes to return
* @return A query with the new constraint
*/
public fun limitToLast(limit: Int): Query
/**
* Creates a query constrained to only return child nodes with the given value.
*
* @param value The value to query for
* @return A query with the new constraint
*/
public fun equalTo(value: String, key: String? = null): Query
/**
* Creates a query constrained to only return child nodes with the given value.
*
* @param value The value to query for
* @return A query with the new constraint
*/
public fun equalTo(value: Double, key: String? = null): Query
/**
* Creates a query constrained to only return child nodes with the given value.
*
* @param value The value to query for
* @return A query with the new constraint
*/
public fun equalTo(value: Boolean, key: String? = null): Query
}
internal expect class NativeDatabaseReference : NativeQuery {
val key: String?
fun push(): NativeDatabaseReference
suspend fun setValueEncoded(encodedValue: Any?)
suspend fun updateEncodedChildren(encodedUpdate: EncodedObject)
fun child(path: String): NativeDatabaseReference
fun onDisconnect(): NativeOnDisconnect
suspend fun removeValue()
suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot
}
/**
* A Firebase reference represents a particular location in your Database and can be used for
* reading or writing data to that Database location.
*
* This class is the starting point for all Database operations. After you've initialized it with
* a URL, you can use it to read data, write data, and to create new DatabaseReferences.
*/
public class DatabaseReference internal constructor(internal val nativeReference: NativeDatabaseReference) : Query(nativeReference) {
/**
* @return The last token in the location pointed to by this reference or null if this reference
* points to the database root
*/
public val key: String? = nativeReference.key
/**
* Create a reference to an auto-generated child location. The child key is generated client-side
* and incorporates an estimate of the server's time for sorting purposes. Locations generated on
* a single client will be sorted in the order that they are created, and will be sorted
* approximately in order across all clients.
*
* @return A DatabaseReference pointing to the new location
*/
public fun push(): DatabaseReference = DatabaseReference(nativeReference.push())
/**
* Get a reference to location relative to this one
*
* @param path The relative path from this reference to the new one that should be created
* @return A new DatabaseReference to the given path
*/
public fun child(path: String): DatabaseReference = DatabaseReference(nativeReference.child(path))
/**
* Provides access to disconnect operations at this location
*
* @return An object for managing disconnect operations at this location
*/
public fun onDisconnect(): OnDisconnect = OnDisconnect(nativeReference.onDisconnect())
@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { this.encodeDefaults = encodeDefaults }"))
public suspend inline fun setValue(value: T?, encodeDefaults: Boolean) {
setValue(value) {
this.encodeDefaults = encodeDefaults
}
}
public suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) {
setValueEncoded(encode(value, buildSettings))
}
@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { this.encodeDefaults = encodeDefaults }"))
public suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) {
setValue(strategy, value) {
this.encodeDefaults = encodeDefaults
}
}
public suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) {
setValueEncoded(encode(strategy, value, buildSettings))
}
@PublishedApi
internal suspend fun setValueEncoded(encodedValue: Any?) {
nativeReference.setValueEncoded(encodedValue)
}
@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { this.encodeDefaults = encodeDefaults }"))
public suspend fun updateChildren(update: Map, encodeDefaults: Boolean) {
updateChildren(update) {
this.encodeDefaults = encodeDefaults
}
}
/**
* Update the specific child keys to the specified values. Passing null in a map to
* updateChildren() will remove the value at the specified location.
*
* @param update The paths to update and their new values
* @return The {@link Task} for this operation.
*/
public suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) {
updateEncodedChildren(
encodeAsObject(update, buildSettings),
)
}
@PublishedApi
internal suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) {
nativeReference.updateEncodedChildren(encodedUpdate)
}
/**
* Set the value at this location to 'null'
*
* @return The {@link Task} for this operation.
*/
public suspend fun removeValue() {
nativeReference.removeValue()
}
/**
* Run a transaction on the data at this location.
*
* @param handler An object to handle running the transaction
*/
public suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot = nativeReference.runTransaction(strategy, buildSettings, transactionUpdate)
}
/**
* A DataSnapshot instance contains data from a Firebase Database location. Any time you read
* Database data, you receive the data as a DataSnapshot.
*
* They are efficiently-generated immutable copies of the data at a Firebase Database location. They
* can't be modified and will never change. To modify data at a location, use a
* [DatabaseReference] reference (e.g. with [DatabaseReference.setValue]).
*/
public expect class DataSnapshot {
/**
* Returns true if the snapshot contains a non-null value.
*
* @return True if the snapshot contains a non-null value, otherwise false
*/
public val exists: Boolean
/**
* @return The key name for the source location of this snapshot or null if this snapshot points
* to the database root.
*/
public val key: String?
/**
* Used to obtain a reference to the source location for this snapshot.
*
* @return A DatabaseReference corresponding to the location that this snapshot came from
*/
public val ref: DatabaseReference
/**
* [value] returns the data contained in this snapshot as native types.
*
* @return The data contained in this snapshot as native types or null if there is no data at this
* location.
*/
public val value: Any?
/**
* [value] returns the data contained in this snapshot as native types.
*
* @return The data contained in this snapshot as native types or null if there is no data at this
* location.
*/
public inline fun value(): T
/**
* [value] returns the data contained in this snapshot as native types.
*
* @return The data contained in this snapshot as native types or null if there is no data at this
* location.
*/
public inline fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T
public fun child(path: String): DataSnapshot
/**
* Indicates whether this snapshot has any children
*
* @return True if the snapshot has any children, otherwise false
*/
public val hasChildren: Boolean
/**
* Gives access to all of the immediate children of this snapshot. Can be used in native for
* loops:
*
* ```
* for (DataSnapshot child : parent.getChildren()) {
* ...
* }
* ```
*
* @return The immediate children of this snapshot
*/
public val children: Iterable
}
/**
* Exception that gets thrown when an operation on Firebase Database fails.
*/
public expect class DatabaseException(message: String?, cause: Throwable?) : RuntimeException
internal expect class NativeOnDisconnect {
suspend fun removeValue()
suspend fun cancel()
suspend fun setEncodedValue(encodedValue: Any?)
suspend fun updateEncodedChildren(encodedUpdate: EncodedObject)
}
/**
* The OnDisconnect class is used to manage operations that will be run on the server when this
* client disconnects. It can be used to add or remove data based on a client's connection status.
* It is very useful in applications looking for 'presence' functionality.
*
* Instances of this class are obtained by calling [DatabaseReference.onDisconnect]
* on a Firebase Database ref.
*/
public class OnDisconnect internal constructor(internal val native: NativeOnDisconnect) {
/**
* Remove the value at this location when the client disconnects
*
* @return The {@link Task} for this operation.
*/
public suspend fun removeValue() {
native.removeValue()
}
/**
* Cancel any disconnect operations that are queued up at this location
*/
public suspend fun cancel() {
native.cancel()
}
/**
* Ensure the data at this location is set to the specified value when the client is disconnected
* (due to closing the browser, navigating to a new page, or network issues).
*
* This method is especially useful for implementing "presence" systems, where a value should be
* changed or cleared when a user disconnects so that they appear "offline" to other users.
*
* @param value The value to be set when a disconnect occurs or null to delete the existing value
*/
@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { this.encodeDefaults = encodeDefaults }"))
public suspend inline fun setValue(value: T?, encodeDefaults: Boolean) {
setValue(value) { this.encodeDefaults = encodeDefaults }
}
/**
* Ensure the data at this location is set to the specified value when the client is disconnected
* (due to closing the browser, navigating to a new page, or network issues).
*
* This method is especially useful for implementing "presence" systems, where a value should be
* changed or cleared when a user disconnects so that they appear "offline" to other users.
*
* @param value The value to be set when a disconnect occurs or null to delete the existing value
*/
public suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) {
setEncodedValue(encode(value, buildSettings))
}
/**
* Ensure the data at this location is set to the specified value when the client is disconnected
* (due to closing the browser, navigating to a new page, or network issues).
*
* This method is especially useful for implementing "presence" systems, where a value should be
* changed or cleared when a user disconnects so that they appear "offline" to other users.
*
* @param value The value to be set when a disconnect occurs or null to delete the existing value
*/
@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { this.encodeDefaults = encodeDefaults }"))
public suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) {
setValue(strategy, value) { this.encodeDefaults = encodeDefaults }
}
/**
* Ensure the data at this location is set to the specified value when the client is disconnected
* (due to closing the browser, navigating to a new page, or network issues).
*
* This method is especially useful for implementing "presence" systems, where a value should be
* changed or cleared when a user disconnects so that they appear "offline" to other users.
*
* @param value The value to be set when a disconnect occurs or null to delete the existing value
*/
public suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) {
setValue(encode(strategy, value, buildSettings))
}
@PublishedApi
internal suspend fun setEncodedValue(encodedValue: Any?) {
native.setEncodedValue(encodedValue)
}
/**
* Ensure the data has the specified child values updated when the client is disconnected
*
* @param update The paths to update, along with their desired values
*/
public suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) {
updateEncodedChildren(
encodeAsObject(update, buildSettings),
)
}
/**
* Ensure the data has the specified child values updated when the client is disconnected
*
* @param update The paths to update, along with their desired values
*/
@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { this.encodeDefaults = encodeDefaults }"))
public suspend fun updateChildren(update: Map, encodeDefaults: Boolean) {
updateChildren(update) {
this.encodeDefaults = encodeDefaults
}
}
@PublishedApi
internal suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) {
native.updateEncodedChildren(encodedUpdate)
}
}