> ## Documentation Index
> Fetch the complete documentation index at: https://docs.insforge.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Kotlin 資料庫 SDK 參考

> 使用 Kotlin SDK 透過 Kotlinx 序列化資料類對 InsForge 表格進行類型安全的插入、更新、刪除、選取和 rpcRaw 作業。

## 安裝

1. Add InsForge dependencies to your project

<Tabs>
  <Tab title="Maven Central">
    build.gradle.kts:

    ```kotlin theme={null}
    repositories {
        mavenLocal() // For local development
        mavenCentral()
    }

    dependencies {
        implementation("dev.insforge:insforge-kotlin:0.1.6")
    }
    ```
  </Tab>

  <Tab title="GitHub Packages">
    First, create a GitHub Personal Access Token:

    * Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
    * Select permission: `read:packages`

    Then configure your project using one of the following methods:

    <AccordionGroup>
      <Accordion title="Option A: Environment Variables">
        settings.gradle.kts (or build.gradle.kts):

        ```kotlin theme={null}
        repositories {
            mavenCentral()
            maven {
                url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin")
                credentials {
                    username = System.getenv("GITHUB_USER") ?: ""
                    password = System.getenv("GITHUB_TOKEN") ?: ""
                }
            }
        }
        ```

        build.gradle.kts:

        ```kotlin theme={null}
        dependencies {
            implementation("dev.insforge:insforge-kotlin:0.1.6")
        }
        ```

        Set environment variables before building:

        ```bash theme={null}
        export GITHUB_USER="your-github-username"
        export GITHUB_TOKEN="your-personal-access-token"
        ```
      </Accordion>

      <Accordion title="Option B: gradle.properties (Local Development)">
        Add credentials to your global Gradle properties file:

        \~/.gradle/gradle.properties:

        ```properties theme={null}
        gpr.user=your-github-username
        gpr.token=ghp_xxxxxxxxxxxx
        ```

        settings.gradle.kts:

        ```kotlin theme={null}
        repositories {
            mavenCentral()
            maven {
                url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin")
                credentials {
                    username = providers.gradleProperty("gpr.user").orNull ?: ""
                    password = providers.gradleProperty("gpr.token").orNull ?: ""
                }
            }
        }
        ```

        build.gradle.kts:

        ```kotlin theme={null}
        dependencies {
            implementation("dev.insforge:insforge-kotlin:0.1.6")
        }
        ```

        <Note>
          The `~/.gradle/gradle.properties` file is stored outside your project, so credentials won't be accidentally committed to version control.
        </Note>
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

2. Initialize InsForge SDK

```kotlin theme={null}
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)
}
```

3. Enable Logging (Optional)

For debugging, you can configure the SDK log level:

```kotlin theme={null}
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 Level | Description                                       |
| --------- | ------------------------------------------------- |
| `NONE`    | No logging (default, recommended for production)  |
| `ERROR`   | Only errors                                       |
| `WARN`    | Warnings and errors                               |
| `INFO`    | Informational messages                            |
| `DEBUG`   | Debug info (request method, URL, response status) |
| `VERBOSE` | Full details (headers, request/response bodies)   |

<Note>
  Use `NONE` or `ERROR` in production to avoid exposing sensitive data in logs.
</Note>

### Android Initialization

1. Add Chrome Custom Tabs dependency to your `build.gradle.kts`:

```kotlin theme={null}
dependencies {
    implementation("androidx.browser:browser:1.9.0")
}
```

2. Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage)

```kotlin theme={null}
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import dev.insforge.createInsforgeClient
import dev.insforge.ai.AI
import dev.insforge.auth.Auth
import dev.insforge.auth.BrowserLauncher
import dev.insforge.auth.ClientType
import dev.insforge.auth.SessionStorage
import dev.insforge.database.Database
import dev.insforge.functions.Functions
import dev.insforge.realtime.Realtime
import dev.insforge.storage.Storage

class InsforgeManager(private val context: Context) {

    val client = createInsforgeClient(
        baseUrl = "https://your-app.insforge.app",
        anonKey = "your-anon-key"
    ) {
        install(Auth) {
            // 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession
            browserLauncher = BrowserLauncher { url ->
                val customTabsIntent = CustomTabsIntent.Builder()
                    .setShowTitle(true)
                    .build()
                // Handle non-Activity context safely
                if (context is Activity) {
                    customTabsIntent.launchUrl(context, Uri.parse(url))
                } else {
                    customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    customTabsIntent.launchUrl(context, Uri.parse(url))
                }
            }

            // 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()
                }
            }

            // 4. set client type for mobile
            clientType = ClientType.MOBILE
        }
        // 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)
    }
}
```

3. Use Jetpack DataStore for Session Storage (Optional)

```kotlin theme={null}
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()

將新記錄插入表格中。

### 範例

```kotlin theme={null}
// 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()

更新表格中的現有記錄。

### 範例

```kotlin theme={null}
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()

從表格中刪除記錄。

### 範例

```kotlin theme={null}
// 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()

從表格中查詢記錄。

### 範例

```kotlin theme={null}
// 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()

執行 SELECT 查詢並傳回原始 JSON 陣列。當您需要處理動態/無型別資料（例如傳回巢狀物件的聯結查詢）時，請使用此方法。

### 範例

```kotlin theme={null}
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()

呼叫 PostgreSQL 預存函式（RPC - 遠端程序呼叫）。此方法可讓您直接呼叫資料庫中定義的 SQL 函式。

### 簽章

```kotlin theme={null}
// 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
```

### 參數

* `functionName` (String) - 要呼叫的 PostgreSQL 函式名稱
* `args` (`Map\<String, Any?\>?`, optional) - 要傳遞給函式的引數

### 實作詳細資料

* **無引數**：使用 GET 請求到 `/api/database/rpc/{functionName}`
* **有引數**：使用 POST 請求（JSON 正文）到 `/api/database/rpc/{functionName}`

### 範例

```kotlin theme={null}
// 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() 範例

當傳回型別在編譯時動態或未知時，使用 `rpcRaw()`。

```kotlin theme={null}
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
```

***

## 篩選條件

| 篩選條件                      | 說明        | 範例                                           |
| ------------------------- | --------- | -------------------------------------------- |
| `.eq(column, value)`      | 等於        | `.eq("status", "active")`                    |
| `.neq(column, value)`     | 不等於       | `.neq("status", "banned")`                   |
| `.gt(column, value)`      | 大於        | `.gt("age", 18)`                             |
| `.gte(column, value)`     | 大於或等於     | `.gte("price", 100)`                         |
| `.lt(column, value)`      | 小於        | `.lt("stock", 10)`                           |
| `.lte(column, value)`     | 小於或等於     | `.lte("priority", 3)`                        |
| `.like(column, pattern)`  | 區分大小寫的模式  | `.like("name", "%Widget%")`                  |
| `.ilike(column, pattern)` | 不區分大小寫的模式 | `.ilike("email", "%@gmail.com")`             |
| `.in(column, list)`       | 值在清單中     | `.in("status", listOf("pending", "active"))` |
| `.isNull(column)`         | 是否為空      | `.isNull("deleted_at")`                      |

```kotlin theme={null}
// Chain multiple filters
val products = insforge.database
    .from("products")
    .select()
    .eq("category", "electronics")
    .gte("price", 50)
    .lte("price", 500)
    .execute<Product>()  // Returns List<Product>
```

***

## 修飾詞

| 修飾詞                         | 說明   | 範例                                        |
| --------------------------- | ---- | ----------------------------------------- |
| `.order(column, ascending)` | 排序結果 | `.order("created_at", ascending = false)` |
| `.limit(count)`             | 限制行數 | `.limit(10)`                              |
| `.range(from, to)`          | 分頁   | `.range(0, 9)`                            |

```kotlin theme={null}
// 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>
```
