Skip to main content

Installation

  1. Add InsForge dependencies to your project
build.gradle.kts:
repositories {
    mavenLocal() // For local development
    mavenCentral()
}

dependencies {
    implementation("dev.insforge:insforge-kotlin:0.1.5")
}
  1. Initialize InsForge SDK
import dev.insforge.createInsforgeClient
import dev.insforge.auth.Auth
import dev.insforge.database.Database
import dev.insforge.storage.Storage
import dev.insforge.functions.Functions
import dev.insforge.realtime.Realtime
import dev.insforge.ai.AI

val client = createInsforgeClient(
    baseUrl = "https://your-app.insforge.app",
    anonKey = "your-api-key"
) {
    install(Auth)
    install(Database)
    install(Storage)
    install(Functions)
    install(Realtime) {
        autoReconnect = true
        reconnectDelay = 5000
    }
    install(AI)
}
  1. Enable Logging (Optional)
For debugging, you can configure the SDK log level:
import dev.insforge.InsforgeLogLevel

val client = createInsforgeClient(
    baseUrl = "https://your-app.insforge.app",
    anonKey = "your-api-key"
) {
    // DEBUG: logs request method/URL and response status
    // VERBOSE: logs full headers and request/response bodies
    logLevel = InsforgeLogLevel.DEBUG

    install(Auth)
    install(Database)
    // ... other modules
}
Log LevelDescription
NONENo logging (default, recommended for production)
ERROROnly errors
WARNWarnings and errors
INFOInformational messages
DEBUGDebug info (request method, URL, response status)
VERBOSEFull details (headers, request/response bodies)
Use NONE or ERROR in production to avoid exposing sensitive data in logs.

Android Initialization

  1. Initialize InsForge SDK (With Local Storage and Browser Launcher for OAuth)
import android.content.Context
import android.content.Intent
import android.net.Uri
import dev.insforge.createInsforgeClient
import dev.insforge.ai.AI
import dev.insforge.auth.Auth
import dev.insforge.database.Database
import dev.insforge.functions.Functions
import dev.insforge.realtime.Realtime
import dev.insforge.storage.Storage
import dev.insforge.auth.BrowserLauncher
import dev.insforge.auth.SessionStorage

class InsforgeManager(private val context: Context) {
    
    val client = createInsforgeClient(
        baseUrl = "https://your-app.insforge.app",
        anonKey = "your-anon-key"
    ) {
        install(Auth) {
            // 1. config BrowserLauncher (for OAuth)
            browserLauncher = BrowserLauncher { url ->
                val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                context.startActivity(intent)
            }
            
            // 2. enable session persistence
            persistSession = true
            
            // 3. config SessionStorage (use SharedPreferences)
            sessionStorage = object : SessionStorage {
                private val prefs = context.getSharedPreferences(
                    "insforge_auth", 
                    Context.MODE_PRIVATE
                )
                
                override suspend fun save(key: String, value: String) {
                    prefs.edit().putString(key, value).apply()
                }
                
                override suspend fun get(key: String): String? {
                    return prefs.getString(key, null)
                }
                
                override suspend fun remove(key: String) {
                    prefs.edit().remove(key).apply()
                }
            }
        }
        // Install Database module
        install(Database)

        // Install Realtime module for real-time subscriptions
        install(Realtime) {
            debug = true
        }
        // Install other modules
        install(Storage)
        install(Functions)
        install(AI)
    }
}
  1. Use Jetpack DataStore for Session Storage (Optional)
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

val Context.authDataStore: DataStore<Preferences> by preferencesDataStore(name = "insforge_auth")

class DataStoreSessionStorage(private val context: Context) : SessionStorage {
    
    override suspend fun save(key: String, value: String) {
        context.authDataStore.edit { prefs ->
            prefs[stringPreferencesKey(key)] = value
        }
    }
    
    override suspend fun get(key: String): String? {
        return context.authDataStore.data.map { prefs ->
            prefs[stringPreferencesKey(key)]
        }.first()
    }
    
    override suspend fun remove(key: String) {
        context.authDataStore.edit { prefs ->
            prefs.remove(stringPreferencesKey(key))
        }
    }
}

// Then use it in your InsForge client
install(Auth) {
    browserLauncher = ...
    persistSession = true
    sessionStorage = DataStoreSessionStorage(context)
}

insert()

Insert new records into a table.

Examples

// Define your data class
@Serializable
data class Post(
    val id: String? = null,
    val title: String,
    val content: String,
    @SerialName("created_at")
    val createdAt: String? = null
)

// Single insert (must wrap in list)
val post = Post(title = "Hello World", content = "My first post!")
val result = insforge.database
    .from("posts")
    .insertTyped(listOf(post))
    .returning()
    .execute<Post>()  // Returns List<Post>

// Bulk insert
val posts = listOf(
    Post(title = "First Post", content = "Hello everyone!"),
    Post(title = "Second Post", content = "Another update.")
)
val result = insforge.database
    .from("posts")
    .insertTyped(posts)
    .returning()
    .execute<Post>()  // Returns List<Post>

update()

Update existing records in a table.

Examples

import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

// Update by ID (using JsonPrimitive)
val result = insforge.database
    .from("posts")
    .update(mapOf("title" to JsonPrimitive("Updated Title")))
    .eq("id", postId)
    .returning()
    .execute<Post>()  // Returns List<Post>

// Update by ID (using buildJsonObject)
val result = insforge.database
    .from("posts")
    .update(buildJsonObject { put("title", "Updated Title") })
    .eq("id", postId)
    .returning()
    .execute<Post>()  // Returns List<Post>

// Update multiple
val result = insforge.database
    .from("tasks")
    .update(buildJsonObject { put("status", "completed") })
    .`in`("id", listOf("task-1", "task-2"))
    .returning()
    .execute<Task>()  // Returns List<Task>

delete()

Delete records from a table.

Examples

// Delete by ID
insforge.database
    .from("posts")
    .delete()
    .eq("id", postId)
    .execute()

// Delete with filter
insforge.database
    .from("sessions")
    .delete()
    .lt("expires_at", Date())
    .execute()

select()

Query records from a table.

Examples

// Get all posts
val posts = insforge.database
    .from("posts")
    .select()
    .execute<Post>()  // Returns List<Post>

// Specific columns
val posts = insforge.database
    .from("posts")
    .select("id, title, content")
    .execute<Post>()  // Returns List<Post>

// With relationships (typed)
@Serializable
data class PostWithComments(
    val id: String,
    val title: String,
    val content: String,
    val comments: List<Comment>
)

val posts = insforge.database
    .from("posts")
    .select("*, comments(id, content)")
    .execute<PostWithComments>()  // Returns List<PostWithComments>

executeRaw()

Execute SELECT query and return raw JSON array. Use this method when you need to work with dynamic/untyped data, such as queries with joins that return nested objects.

Examples

import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

// Query with relationships - raw JSON access
val result = insforge.database
    .from("tweets")
    .select("id, content, profiles!tweets_user_id_fkey(username)")
    .executeRaw()  // Returns JsonArray

result.forEach { element ->
    val obj = element.jsonObject
    val id = obj["id"]?.jsonPrimitive?.content
    val content = obj["content"]?.jsonPrimitive?.content
    val profile = obj["profiles"]?.jsonObject
    val username = profile?.get("username")?.jsonPrimitive?.content

    println("Tweet by $username: $content")
}

// Dynamic queries where structure is unknown
val rawData = insforge.database
    .from("dynamic_table")
    .select()
    .executeRaw()

// Process raw JSON as needed
rawData.forEach { element ->
    val obj = element.jsonObject
    obj.keys.forEach { key ->
        println("$key: ${obj[key]}")
    }
}

rpc()

Call PostgreSQL stored functions (RPC - Remote Procedure Call). This method allows you to directly invoke SQL functions defined in your database.

Signature

// Typed RPC call - deserializes response to specified type
suspend inline fun <reified T> rpc(
    functionName: String,
    args: Map<String, Any?>? = null
): T

// Raw RPC call - returns JsonElement for dynamic processing
suspend fun rpcRaw(
    functionName: String,
    args: Map<String, Any?>? = null
): JsonElement

Parameters

  • functionName (String) - Name of the PostgreSQL function to call
  • args (Map\<String, Any?\>?, optional) - Arguments to pass to the function

Implementation Details

  • No arguments: Uses GET request to /api/database/rpc/{functionName}
  • With arguments: Uses POST request with JSON body to /api/database/rpc/{functionName}

Examples

// Define response data classes
@Serializable
data class UserStats(
    val totalPosts: Int,
    val totalLikes: Int,
    val joinedAt: String
)

@Serializable
data class User(
    val id: String,
    val name: String,
    val email: String
)

// Call function with parameters
val stats = insforge.database.rpc<UserStats>(
    "get_user_stats",
    mapOf("user_id" to 123)
)
println("Total posts: ${stats.totalPosts}")

// Call function without parameters
val users = insforge.database.rpc<List<User>>("get_all_active_users")
users.forEach { user ->
    println("User: ${user.name}")
}

// Call function returning a single value
val count = insforge.database.rpc<Int>("count_active_posts")
println("Active posts: $count")

// Call function with multiple parameters
val result = insforge.database.rpc<List<Post>>(
    "search_posts",
    mapOf(
        "search_term" to "kotlin",
        "limit" to 10,
        "offset" to 0
    )
)

rpcRaw() Examples

Use rpcRaw() when the return type is dynamic or unknown at compile time.
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

// Get raw JSON response
val result = insforge.database.rpcRaw(
    "some_dynamic_function",
    mapOf("param" to "value")
)

// Process based on actual response structure
when (result) {
    is JsonArray -> {
        result.forEach { element ->
            val obj = element.jsonObject
            println("Item: ${obj["name"]?.jsonPrimitive?.content}")
        }
    }
    is JsonObject -> {
        println("Single result: ${result["data"]}")
    }
    is JsonPrimitive -> {
        println("Value: ${result.content}")
    }
}

// Handle complex nested structures
val complexResult = insforge.database.rpcRaw("get_dashboard_data")
val dashboard = complexResult.jsonObject
val userCount = dashboard["user_count"]?.jsonPrimitive?.int
val recentPosts = dashboard["recent_posts"]?.jsonArray

Filters

FilterDescriptionExample
.eq(column, value)Equals.eq("status", "active")
.neq(column, value)Not equals.neq("status", "banned")
.gt(column, value)Greater than.gt("age", 18)
.gte(column, value)Greater than or equal.gte("price", 100)
.lt(column, value)Less than.lt("stock", 10)
.lte(column, value)Less than or equal.lte("priority", 3)
.like(column, pattern)Case-sensitive pattern.like("name", "%Widget%")
.ilike(column, pattern)Case-insensitive pattern.ilike("email", "%@gmail.com")
.in(column, list)Value in list.in("status", listOf("pending", "active"))
.isNull(column)Is null.isNull("deleted_at")
// Chain multiple filters
val products = insforge.database
    .from("products")
    .select()
    .eq("category", "electronics")
    .gte("price", 50)
    .lte("price", 500)
    .execute<Product>()  // Returns List<Product>

Modifiers

ModifierDescriptionExample
.order(column, ascending)Sort results.order("created_at", ascending = false)
.limit(count)Limit rows.limit(10)
.range(from, to)Pagination.range(0, 9)
// Pagination with sorting
val posts = insforge.database
    .from("posts")
    .select()
    .order("created_at", ascending = false)
    .range(0, 9)
    .limit(10)
    .execute<Post>()  // Returns List<Post>

Jetpack Compose Integration

@Composable
fun PostListScreen() {
    var posts by remember { mutableStateOf<List<Post>>(emptyList()) }
    var isLoading by remember { mutableStateOf(true) }

    LaunchedEffect(Unit) {
        try {
            posts = insforge.database
                .from("posts")
                .select()
                .order("created_at", ascending = false)
                .execute<Post>()  // Returns List<Post>
        } catch (e: Exception) {
            Log.e("PostList", "Error loading posts", e)
        } finally {
            isLoading = false
        }
    }

    if (isLoading) {
        CircularProgressIndicator()
    } else {
        LazyColumn {
            items(posts) { post ->
                PostItem(post = post)
            }
        }
    }
}

@Composable
fun PostItem(post: Post) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = post.title,
                style = MaterialTheme.typography.titleMedium
            )
            Spacer(modifier = Modifier.height(4.dp))
            Text(
                text = post.content,
                style = MaterialTheme.typography.bodyMedium
            )
        }
    }
}