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

# Kotlin SDK - Ktor Plugin

> Ktor server plugin that ties a Nexus client to the application lifecycle.

`nexus-ktor-plugin` installs a `NexusClient` into a Ktor server application and ties it to the
application's lifecycle.

It is a **separate artifact**, so a consumer that does not run Ktor never resolves
`ktor-server-core`.

## Installation

```kotlin theme={null}
dependencies {
    implementation("dev.westyx.nexus:nexus-ktor-plugin:0.11.0")

    // Still required: a Ktor *client* engine. A Ktor server dependency does not
    // bring one with it.
    implementation("io.ktor:ktor-client-cio:3.5.1")
}
```

## Usage

```kotlin theme={null}
import dev.westyx.nexus.ktor.Nexus
import dev.westyx.nexus.ktor.nexus
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun Application.module() {
    install(Nexus) {
        endpoint = "https://my-service.westyx.dev"
        apiKey   = System.getenv("NEXUS_API_KEY")
    }

    routing {
        get("/greeting") {
            call.respondText(nexus.getString("greeting", "hi"))
        }
        get("/flags/checkout") {
            call.respondText(nexus.getFlag("new-checkout").toString())
        }
    }
}
```

`Application.nexus` returns the installed client. Without the plugin it raises an
`IllegalStateException` naming the installation call rather than a bare missing-attribute error.

There is also `installNexus { … }` as a shorthand for `install(Nexus) { … }`.

## Configuration

```kotlin theme={null}
install(Nexus) {
    endpoint             = "https://my-service.westyx.dev"
    apiKey               = System.getenv("NEXUS_API_KEY")
    ttl                  = 60.seconds
    wif                  = WifConfig(enabled = true)
    observer             = MyNexusObserver()
    sseReconnectCooldown = intArrayOf(5, 10, 20)
    connectStream        = true
    engine               = OkHttp.create()
    logger               = null   // null = the Ktor application logger
}
```

| Field                  | Default      | Notes                                                                           |
| ---------------------- | ------------ | ------------------------------------------------------------------------------- |
| `endpoint`             | `""`         | Maps to `NexusConfig.baseUrl`. Must be `https` (loopback excepted).             |
| `apiKey`               | `null`       | Required unless `wif` is enabled.                                               |
| `ttl`                  | `60.seconds` | Must be greater than zero.                                                      |
| `wif`                  | `null`       | See [Workload identity](/sdks/kotlin/workload-identity).                        |
| `observer`             | no-op        | See [Stream observer](/sdks/kotlin/stream-observer).                            |
| `sseReconnectCooldown` | `null`       | Minutes; `null` means `[5, 10, 20, 40, 60]`.                                    |
| `connectStream`        | `true`       | Set `false` for TTL polling only.                                               |
| `engine`               | `null`       | Resolved from the classpath when null.                                          |
| `logger`               | `null`       | Null means the Ktor `application.log`, so SDK output joins the application log. |

Everything else behaves exactly as in [Configuration](/sdks/kotlin/configuration) - the plugin builds a
`NexusConfig` from these fields.

## Lifecycle

**Startup.** The client is created, and performs its initial sync, while the plugin installs. A
bad endpoint, a rejected key or an unresolvable WIF credential therefore fails
`embeddedServer(...).start()` rather than surfacing as a 500 on whichever request first happens to
touch a config. If `connectStream` is true, the live-update stream opens here too.

**Shutdown.** On `ApplicationStopping` the plugin closes the client, which cancels the stream,
releases both HTTP clients and the WIF credential source, and removes any materialised file
secrets. This is the plugin's main reason to exist: a stream coroutine that outlives the
application would hold a connection and a thread pool open in a process that believes it has shut
down. The plugin's test suite asserts that `streamStatus` is `DISCONNECTED` and the client is
closed once the application has stopped.

```
install(Nexus)   -> create client -> initial sync -> publish as Application.nexus -> connectStream()
ApplicationStopping -> client.close() -> stream cancelled, clients closed, file secrets removed
```

Because installation performs a suspending initial sync in a non-suspending Ktor hook, it blocks
the startup thread until the sync completes. That is deliberate: application startup is
synchronous, and fail-fast is the point.

## Accessing the client outside a route

```kotlin theme={null}
fun Application.scheduleReload() {
    val client = nexus
    launch {
        while (isActive) {
            delay(5.minutes)
            runCatching { client.sync() }
        }
    }
}
```
