
Compile-time dependency injection turning plain interfaces into modules with generated implementations, binding-based wiring, compiler-checked dependencies, suspend-capable construction, and easy per-member overrides.
= Inara :toc: macro :toclevels: 2 :idprefix: :idseparator: - :icons: font :source-highlighter: highlight.js :version: 0.1.0
[.lead] Compile-time dependency injection for Kotlin Multiplatform: your modules are just interfaces.
Inara turns a plain Kotlin interface into a dependency-injection module.
You declare what a module offers as ordinary interface members, and you decide how each member is produced by supplying a Binding when you build the module.
A KSP processor generates the implementation.
@DIModule interface NetworkModule { val baseUrl: String fun httpClient(): HttpClient }
val network = NetworkModule( // <1> baseUrl = Binding.value("https://api.example.com"), httpClient = Binding.single { HttpClient(baseUrl) }, // <2> )
<1> A builder function generated by Inara, named after the interface. <2> A binding body sees the module as its receiver, so it can read any other member. <3> Consumers call a normal method on a normal interface.
No reflection. No service locator. No runtime graph. Just an interface, a generated implementation, and a compiler that tells you when a dependency is missing.
toc::[]
== Why Inara
Your module is its own API.
A @DIModule interface is the contract consumers depend on.
There is no separate "component" type, no qualifier annotation, and no get<T>() lookup.
Opinionated, by design.
There is no global context and no mutable context.
Business code stays independent from the dependency-injection layer: it has no notion that Inara even exists.
@DIFactory takes that further still, building classes that do not import a single Inara type.
Wiring is a value, not a configuration language.
A module instance is built by calling a generated function with one Binding per member.
Bindings are ordinary values that you can pass around, store, and swap.
Every dependency is checked by the compiler. Forget a binding, and the builder call does not compile. There is no runtime "unresolved dependency" failure.
suspend members are native.
Mark a member suspend and it is bound with a SuspendBinding whose bodies can suspend.
Asynchronous construction propagates the way it should, instead of hiding behind runBlocking.
Overriding is first-class.
Every module gets a generated copy(...) function, so building a test variant means overriding one member and keeping the rest.
Multiplatform, without surprises. Inara behaves exactly the same on every platform it supports. The only exception is JS and Wasm, where thread synchronization is a no-op, since there is no thread to synchronize.
== Installation
The shortest setup that works, on a JVM project:
plugins { kotlin("jvm") id("com.google.devtools.ksp") // <1> }
<1> Inara runs on KSP but does not install it: you apply the plugin yourself.
<2> The runtime: Binding types and annotations.
<3> The processor that generates module implementations.
For Kotlin Multiplatform and the other setups, see the https://kosi-libs.org/inara/latest/core/setup.html[setup page].
== Documentation
The full documentation is on https://kosi-libs.org/inara[kosi-libs.org].
= Inara :toc: macro :toclevels: 2 :idprefix: :idseparator: - :icons: font :source-highlighter: highlight.js :version: 0.1.0
[.lead] Compile-time dependency injection for Kotlin Multiplatform: your modules are just interfaces.
Inara turns a plain Kotlin interface into a dependency-injection module.
You declare what a module offers as ordinary interface members, and you decide how each member is produced by supplying a Binding when you build the module.
A KSP processor generates the implementation.
@DIModule interface NetworkModule { val baseUrl: String fun httpClient(): HttpClient }
val network = NetworkModule( // <1> baseUrl = Binding.value("https://api.example.com"), httpClient = Binding.single { HttpClient(baseUrl) }, // <2> )
<1> A builder function generated by Inara, named after the interface. <2> A binding body sees the module as its receiver, so it can read any other member. <3> Consumers call a normal method on a normal interface.
No reflection. No service locator. No runtime graph. Just an interface, a generated implementation, and a compiler that tells you when a dependency is missing.
toc::[]
== Why Inara
Your module is its own API.
A @DIModule interface is the contract consumers depend on.
There is no separate "component" type, no qualifier annotation, and no get<T>() lookup.
Opinionated, by design.
There is no global context and no mutable context.
Business code stays independent from the dependency-injection layer: it has no notion that Inara even exists.
@DIFactory takes that further still, building classes that do not import a single Inara type.
Wiring is a value, not a configuration language.
A module instance is built by calling a generated function with one Binding per member.
Bindings are ordinary values that you can pass around, store, and swap.
Every dependency is checked by the compiler. Forget a binding, and the builder call does not compile. There is no runtime "unresolved dependency" failure.
suspend members are native.
Mark a member suspend and it is bound with a SuspendBinding whose bodies can suspend.
Asynchronous construction propagates the way it should, instead of hiding behind runBlocking.
Overriding is first-class.
Every module gets a generated copy(...) function, so building a test variant means overriding one member and keeping the rest.
Multiplatform, without surprises. Inara behaves exactly the same on every platform it supports. The only exception is JS and Wasm, where thread synchronization is a no-op, since there is no thread to synchronize.
== Installation
The shortest setup that works, on a JVM project:
plugins { kotlin("jvm") id("com.google.devtools.ksp") // <1> }
<1> Inara runs on KSP but does not install it: you apply the plugin yourself.
<2> The runtime: Binding types and annotations.
<3> The processor that generates module implementations.
For Kotlin Multiplatform and the other setups, see the https://kosi-libs.org/inara/latest/core/setup.html[setup page].
== Documentation
The full documentation is on https://kosi-libs.org/inara[kosi-libs.org].