> ## 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.

# 函式 SDK 參考

> 使用 InsForge Kotlin SDK 呼叫無伺服器函式

## 安裝

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)
}
```

***

## invoke()

透過 slug 呼叫無伺服器函式。

### 參數

* `slug` (String) - 函式 slug 識別碼
* `body` (Any?, optional) - 請求本體（將被 JSON 序列化）

### 傳回值

```kotlin theme={null}
T // Typed response (reified generic)
```

### 範例

```kotlin theme={null}
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName

// Define response data class
@Serializable
data class HelloResponse(
    val message: String,
    val timestamp: String
)

// Invoke function with typed response
val response = client.functions.invoke<HelloResponse>(
    slug = "hello-world",
    body = mapOf("name" to "World")
)

println(response.message)  // "Hello, World!"
```

### 帶有類型化請求的範例

```kotlin theme={null}
@Serializable
data class GreetingRequest(
    val name: String,
    val greeting: String
)

@Serializable
data class GreetingResponse(
    val message: String,
    val timestamp: String
)

val response = client.functions.invoke<GreetingResponse>(
    slug = "hello-world",
    body = GreetingRequest(name = "Kotlin", greeting = "Hello")
)
```

***

## invokeRaw()

呼叫函式並取得原始 HTTP 回應。

### 範例

```kotlin theme={null}
val response = client.functions.invokeRaw(
    slug = "generate-pdf",
    body = mapOf("documentId" to "doc-123")
)

// Access raw response
val bytes = response.readBytes()
val contentType = response.contentType()
```

***

## 管理員函式

下列方法需要管理員/服務角色驗證。

<Warning>
  這些方法要求用戶端使用**服務角色金鑰**而不是 anon 金鑰初始化。服務角色金鑰具有提升的權限，應僅在安全的伺服器端環境中使用，決不能在用戶端代碼中使用。

  ```kotlin theme={null}
  // Initialize client with service role key for admin operations
  val adminClient = createInsforgeClient(
      baseUrl = "https://your-app.insforge.app",
      anonKey = "your-service-role-key"  // Use service role key or api key, not anon key
  ) {
      install(Functions)
  }
  ```
</Warning>

### listFunctions()

列出所有函式。

```kotlin theme={null}
val functions = client.functions.listFunctions()

functions.forEach { fn ->
    println("${fn.name} (${fn.slug}) - ${fn.status}")
}
```

### getFunction()

取得特定函式的詳細資料。

```kotlin theme={null}
val details = client.functions.getFunction("hello-world")

println("Name: ${details.name}")
println("Slug: ${details.slug}")
println("Status: ${details.status}")
println("Code: ${details.code}")
```

### createFunction()

<Note>
  目前，InsForge 僅支援在 Deno 環境中執行的 JavaScript/TypeScript 函式。
</Note>

建立新函式。

```kotlin theme={null}
val result = client.functions.createFunction(
    name = "Hello World",
    code = """
        export default async function(req) {
            const body = await req.json()
            return new Response(JSON.stringify({
                message: `Hello, ${'$'}{body.name}!`,
                timestamp: new Date().toISOString()
            }), {
                headers: { "Content-Type": "application/json" }
            })
        }
    """.trimIndent(),
    slug = "hello-world",  // Optional, auto-generated from name if not provided
    description = "A simple greeting function",
    status = "active"  // "draft" or "active"
)

println("Created function: ${result.slug}")
```

### updateFunction()

更新現有函式。

```kotlin theme={null}
val result = client.functions.updateFunction(
    slug = "hello-world",
    name = "Hello World v2",
    code = """
        export default async function(req) {
            const body = await req.json()
            return new Response(JSON.stringify({
                message: `Hello, ${'$'}{body.name}! Welcome to v2.`,
                version: 2
            }), {
                headers: { "Content-Type": "application/json" }
            })
        }
    """.trimIndent(),
    status = "active"
)
```

### deleteFunction()

刪除函式。

```kotlin theme={null}
client.functions.deleteFunction("old-function")
```

***

## 錯誤處理

```kotlin theme={null}
import dev.insforge.exceptions.InsforgeHttpException

try {
    val response = client.functions.invoke<MyResponse>("my-function")
    println("Success: $response")
} catch (e: InsforgeHttpException) {
    println("HTTP Error ${e.statusCode}: ${e.message}")
    println("Error code: ${e.error}")
    e.nextActions?.let { actions ->
        println("Suggested actions: $actions")
    }
} catch (e: Exception) {
    println("Unexpected error: ${e.message}")
}
```

***

## 模型參考

### FunctionMetadata

```kotlin theme={null}
@Serializable
data class FunctionMetadata(
    val id: String,
    val slug: String,
    val name: String,
    val description: String? = null,
    val status: String,  // "draft", "active", "error"
    @SerialName("created_at") val createdAt: String? = null,
    @SerialName("updated_at") val updatedAt: String? = null,
    @SerialName("deployed_at") val deployedAt: String? = null
)
```

### FunctionDetails

```kotlin theme={null}
@Serializable
data class FunctionDetails(
    val id: String,
    val slug: String,
    val name: String,
    val description: String? = null,
    val code: String,
    val status: String,  // "draft", "active", "error"
    @SerialName("created_at") val createdAt: String,
    @SerialName("updated_at") val updatedAt: String? = null,
    @SerialName("deployed_at") val deployedAt: String? = null
)
```
