or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdbinding-dsl.mdcontainer-configuration.mddirect-access.mdindex.mdlazy-property-delegation.mdscoping-and-context.md

advanced-features.mddocs/

0

# Advanced Features

1

2

Advanced dependency injection patterns including constructor injection, external sources, binding search, sub-DI creation, and specialized utilities for complex dependency management scenarios.

3

4

## Capabilities

5

6

### Constructor Injection (New Operator)

7

8

Automatic constructor parameter injection for creating instances with dependency resolution without explicit binding definitions.

9

10

```kotlin { .api }

11

/**

12

* Create instance with no-parameter constructor injection

13

* @param constructor Constructor function reference

14

* @return New instance with all dependencies automatically injected

15

*/

16

fun <T> DirectDIAware.new(constructor: () -> T): T

17

18

/**

19

* Create instance with 1-parameter constructor injection

20

* @param constructor Constructor function reference

21

* @return New instance with P1 automatically injected via instance<P1>()

22

*/

23

fun <P1, T> DirectDIAware.new(constructor: (P1) -> T): T

24

25

/**

26

* Create instance with 2-parameter constructor injection

27

* @param constructor Constructor function reference

28

* @return New instance with P1, P2 automatically injected

29

*/

30

fun <P1, P2, T> DirectDIAware.new(constructor: (P1, P2) -> T): T

31

32

/**

33

* Create instance with 3-parameter constructor injection

34

* @param constructor Constructor function reference

35

* @return New instance with P1, P2, P3 automatically injected

36

*/

37

fun <P1, P2, P3, T> DirectDIAware.new(constructor: (P1, P2, P3) -> T): T

38

39

// ... continues up to 22 parameter overloads

40

41

/**

42

* Exception thrown when a constructor parameter is not used in injection

43

* This helps detect unused parameter instances in new operator calls

44

*/

45

class DI.UnusedParameterException(message: String, cause: Exception? = null) : RuntimeException(message, cause)

46

```

47

48

### External Sources

49

50

Fallback mechanism for dependency resolution when bindings are not found in the container, enabling integration with other DI frameworks or dynamic resolution.

51

52

```kotlin { .api }

53

/**

54

* External source interface for fallback dependency resolution

55

*/

56

interface ExternalSource {

57

/**

58

* Attempt to provide a factory for the given key when not found in container

59

* @param key The binding key that was not found

60

* @param context The resolution context

61

* @return Factory function or null if this source cannot provide the dependency

62

*/

63

fun <C : Any, A, T : Any> getFactory(

64

key: DI.Key<C, A, T>,

65

context: C

66

): ((A) -> T)?

67

}

68

69

/**

70

* External sources list for registering fallback resolution mechanisms

71

* External sources are consulted in order when bindings are not found

72

*/

73

val DI.MainBuilder.externalSources: MutableList<ExternalSource>

74

```

75

76

### Sub-DI Creation

77

78

Creation of child DI containers that inherit from parent containers while allowing additional bindings and overrides.

79

80

```kotlin { .api }

81

/**

82

* Create a sub-DI container with additional bindings

83

* @param allowSilentOverride Whether to allow implicit binding overrides

84

* @param copy Copy strategy for inheriting bindings from parent

85

* @param init Configuration block for additional bindings

86

* @return New DI container extending the current one

87

*/

88

fun DirectDIAware.subDI(

89

allowSilentOverride: Boolean = false,

90

copy: Copy = Copy.NonCached,

91

init: DI.MainBuilder.() -> Unit

92

): DI

93

94

/**

95

* Copy strategies for sub-DI creation

96

*/

97

sealed class Copy {

98

/** Copy no bindings from parent */

99

object None : Copy()

100

101

/** Copy all bindings from parent */

102

object All : Copy()

103

104

/** Copy only non-cached bindings (providers, factories) */

105

object NonCached : Copy()

106

}

107

```

108

109

### Factory Currying

110

111

Utilities for converting factory functions into provider functions by pre-supplying arguments.

112

113

```kotlin { .api }

114

/**

115

* Convert a factory function to a provider by currying with an argument

116

* @param arg Function that provides the argument for the factory

117

* @return Provider function that calls the factory with the supplied argument

118

*/

119

fun <A, T> ((A) -> T).toProvider(arg: () -> A): () -> T

120

```

121

122

### Binding Search and Discovery

123

124

Advanced binding search capabilities for finding and inspecting bindings within the DI container.

125

126

```kotlin { .api }

127

/**

128

* Specifications for searching bindings in the container

129

*/

130

data class SearchSpecs(

131

val contextType: TypeToken<*>? = null,

132

val argType: TypeToken<*>? = null,

133

val type: TypeToken<*>? = null,

134

val tag: Any? = null

135

)

136

137

/**

138

* DSL for building search specifications

139

*/

140

class SearchDSL {

141

/**

142

* Search for bindings with specific context type

143

*/

144

fun <C : Any> context(type: TypeToken<C>)

145

146

/**

147

* Search for bindings with specific argument type

148

*/

149

fun <A> argument(type: TypeToken<A>)

150

151

/**

152

* Search for bindings with specific return type

153

*/

154

fun <T> type(type: TypeToken<T>)

155

156

/**

157

* Search for bindings with specific tag

158

*/

159

fun tag(tag: Any)

160

}

161

162

/**

163

* DSL for finding bindings in the container

164

*/

165

class FindDSL : SearchDSL() {

166

// Additional methods for finding specific binding patterns

167

}

168

169

/**

170

* Find all bindings matching the search criteria

171

* @param f DSL block for specifying search criteria

172

* @return List of matching bindings with their keys and context matches

173

*/

174

fun DITree.findAllBindings(

175

f: FindDSL.() -> Unit

176

): List<Triple<DI.Key<*, *, *>, DIBinding<*, *, *>, ContextMatch>>

177

178

/**

179

* Context match information for search results

180

*/

181

enum class ContextMatch {

182

EXACT, // Exact context type match

183

SUPER, // Context is a supertype

184

SUB // Context is a subtype

185

}

186

```

187

188

### Late Initialization Support

189

190

Patterns and utilities for delayed DI initialization in scenarios where immediate container creation is not possible.

191

192

```kotlin { .api }

193

/**

194

* Late-initialized DI container for manual setup scenarios

195

*/

196

class LateInitDI : DI {

197

/**

198

* Base DI instance that must be set before use

199

* @throws UninitializedPropertyAccessException if accessed before initialization

200

*/

201

lateinit var baseDI: DI

202

203

// All DI operations delegate to baseDI after initialization

204

}

205

206

/**

207

* Property delegate for late-initialized DI-aware properties

208

*/

209

class LateinitDIProperty<T>(

210

private val getDI: () -> DI,

211

private val creator: DirectDI.() -> T

212

) : LazyDelegate<T>

213

```

214

215

### Multi-Argument Support

216

217

Advanced binding patterns for functions and factories that require multiple arguments beyond single-parameter factories.

218

219

```kotlin { .api }

220

/**

221

* Multi-argument factory binding support

222

* Allows binding factories that take multiple typed arguments

223

*/

224

interface MultiArgFactory<T> {

225

fun with(vararg args: Any): T

226

}

227

228

/**

229

* Multi-argument binding DSL support

230

*/

231

fun <T : Any> DI.Builder.multiArgFactory(

232

creator: DirectDI.(Array<Any>) -> T

233

): MultiArgFactory<T>

234

```

235

236

### Error Information Enhancement

237

238

Enhanced error reporting with detailed container information and binding diagnostics.

239

240

```kotlin { .api }

241

/**

242

* Enhanced exception with detailed binding information

243

*/

244

class DI.NotFoundException(

245

val key: Key<*, *, *>,

246

message: String

247

) : RuntimeException(message) {

248

/** The binding key that was not found */

249

val key: Key<*, *, *>

250

}

251

252

/**

253

* Exception for dependency cycles with detailed loop information

254

*/

255

class DI.DependencyLoopException(message: String) : RuntimeException(message)

256

257

/**

258

* Exception for binding override conflicts

259

*/

260

class DI.OverridingException(message: String) : RuntimeException(message)

261

262

/**

263

* Exception for search operations that return no results

264

*/

265

class DI.NoResultException(

266

val search: SearchSpecs,

267

message: String

268

) : RuntimeException(message)

269

270

// Error configuration for detailed diagnostics

271

var DI.MainBuilder.fullDescriptionOnError: Boolean

272

var DI.MainBuilder.fullContainerTreeOnError: Boolean

273

```

274

275

### Type System Integration

276

277

Advanced type handling and generic type preservation for complex dependency scenarios.

278

279

```kotlin { .api }

280

/**

281

* Typed wrapper for values with their type information

282

* @param A Type of the wrapped value

283

*/

284

interface Typed<A> {

285

/** TypeToken representing the type of the value */

286

val type: TypeToken<A>

287

288

/** The actual typed value */

289

val value: A

290

}

291

292

/**

293

* Create a typed wrapper with immediate value

294

* @param type TypeToken for the value type

295

* @param value The value to wrap

296

* @return Typed wrapper containing the value and its type

297

*/

298

fun <A> Typed(type: TypeToken<A>, value: A): Typed<A>

299

300

/**

301

* Create a typed wrapper with lazy value evaluation

302

* @param type TypeToken for the value type

303

* @param func Function that provides the value when needed

304

* @return Typed wrapper with lazy value evaluation

305

*/

306

fun <A> Typed(type: TypeToken<A>, func: () -> A): Typed<A>

307

```

308

309

### Container Inspection and Debugging

310

311

Utilities for inspecting and debugging DI container state and binding resolution.

312

313

```kotlin { .api }

314

/**

315

* Container tree interface for inspecting binding structure

316

*/

317

interface DITree {

318

/**

319

* Find bindings matching search criteria

320

*/

321

fun find(search: SearchSpecs): List<DI.Key<*, *, *>>

322

323

/**

324

* Get all registered bindings

325

*/

326

fun allBindings(): Map<DI.Key<*, *, *>, DIBinding<*, *, *>>

327

328

/**

329

* Get container description for debugging

330

*/

331

fun containerDescription(): String

332

}

333

334

/**

335

* Binding information for diagnostics

336

*/

337

data class BindingInfo(

338

val key: DI.Key<*, *, *>,

339

val binding: DIBinding<*, *, *>,

340

val scope: String?,

341

val description: String

342

)

343

```

344

345

**Usage Examples:**

346

347

```kotlin

348

// Constructor injection with new operator

349

class OrderService : DirectDIAware {

350

override val directDI: DirectDI = appDirectDI

351

352

fun processOrder(order: Order) {

353

// Automatic constructor parameter injection

354

val validator = new(::OrderValidator) // Injects dependencies automatically

355

val processor = new(::PaymentProcessor) // Finds and injects required services

356

val emailer = new(::OrderEmailService) // Injects email service and templates

357

358

validator.validate(order)

359

val payment = processor.process(order.paymentInfo)

360

emailer.sendConfirmation(order, payment)

361

}

362

363

// Multi-parameter constructor injection

364

fun createComplexService() {

365

val service = new { db: Database, cache: Cache, logger: Logger, config: Config ->

366

ComplexService(db, cache, logger, config) // All 4 params auto-injected

367

}

368

}

369

}

370

371

// External sources for fallback resolution

372

class SpringIntegrationSource : ExternalSource {

373

private val springContext: ApplicationContext = getSpringContext()

374

375

override fun <C : Any, A, T : Any> getFactory(

376

key: DI.Key<C, A, T>,

377

context: C

378

): ((A) -> T)? {

379

return try {

380

val bean = springContext.getBean(key.type.jvmType as Class<T>)

381

{ _ -> bean } // Return factory that ignores argument and returns Spring bean

382

} catch (e: NoSuchBeanDefinitionException) {

383

null // Let DI continue looking

384

}

385

}

386

}

387

388

val integratedDI = DI {

389

// Regular Kodein-DI bindings

390

bind<UserService>() with singleton { UserServiceImpl() }

391

392

// Add Spring as fallback

393

externalSources.add(SpringIntegrationSource())

394

395

// Now can inject Spring beans even if not bound in Kodein-DI

396

}

397

398

// Sub-DI for testing and isolation

399

class TestingService : DirectDIAware {

400

override val directDI = productionDI.direct

401

402

fun runTestScenario() {

403

// Create test-specific DI with mocks

404

val testDI = subDI(copy = Copy.NonCached) {

405

bind<EmailService>(overrides = true) with singleton { MockEmailService() }

406

bind<PaymentProcessor>(overrides = true) with singleton { TestPaymentProcessor() }

407

408

// Additional test-only services

409

bind<TestDataProvider>() with singleton { TestDataProviderImpl() }

410

}

411

412

// Use test DI for this scenario

413

val testDirectDI = testDI.direct

414

val service = testDirectDI.new(::OrderService) // Uses mocked dependencies

415

service.processTestOrder()

416

}

417

}

418

419

// Factory currying for partial application

420

class DataProcessingService : DirectDIAware {

421

override val directDI = appDirectDI

422

423

fun setupProcessors() {

424

// Get factory and curry it with configuration

425

val processorFactory = directDI.factory<ProcessingConfig, DataProcessor>()

426

427

val standardConfig = ProcessingConfig(threads = 4, batchSize = 100)

428

val standardProcessor = processorFactory.toProvider { standardConfig }

429

430

val highPerfConfig = ProcessingConfig(threads = 8, batchSize = 200)

431

val highPerfProcessor = processorFactory.toProvider { highPerfConfig }

432

433

// Now have two providers with different configurations

434

processStandardData(standardProcessor())

435

processHighVolumeData(highPerfProcessor())

436

}

437

}

438

439

// Binding search and discovery

440

class DIInspector(private val di: DI) {

441

fun inspectBindings() {

442

val tree = di.container.tree

443

444

// Find all singleton bindings

445

val singletons = tree.findAllBindings {

446

// Search by binding type patterns

447

}.filter { (_, binding, _) ->

448

binding.javaClass.simpleName.contains("Singleton")

449

}

450

451

println("Found ${singletons.size} singleton bindings:")

452

singletons.forEach { (key, binding, match) ->

453

println(" ${key.description} -> ${binding.description}")

454

}

455

456

// Find all bindings for a specific type

457

val userServiceBindings = tree.find(SearchSpecs(

458

type = generic<UserService>(),

459

tag = null

460

))

461

462

println("UserService bindings: $userServiceBindings")

463

}

464

465

fun diagnosticReport(): String {

466

return try {

467

di.container.tree.containerDescription()

468

} catch (e: Exception) {

469

"Error generating diagnostic report: ${e.message}"

470

}

471

}

472

}

473

474

// Late initialization patterns

475

class ApplicationContext {

476

private val lateInitDI = LateInitDI()

477

478

// Services that depend on DI

479

private val userService: UserService by lateInitDI.instance()

480

private val configService: ConfigService by lateInitDI.instance()

481

482

fun initialize(config: AppConfig) {

483

// Set up DI after configuration is available

484

lateInitDI.baseDI = DI {

485

bind<AppConfig>() with instance(config)

486

bind<UserService>() with singleton { UserServiceImpl(instance()) }

487

bind<ConfigService>() with singleton { ConfigServiceImpl(instance()) }

488

}

489

490

// Now property delegates can resolve

491

configService.loadConfiguration()

492

userService.initializeUserData()

493

}

494

}

495

496

// Advanced error handling and diagnostics

497

class RobustDIService : DIAware {

498

override val di = DI {

499

fullDescriptionOnError = true // Include full type names in errors

500

fullContainerTreeOnError = true // Include all bindings in NotFoundException

501

502

bind<PrimaryService>() with singleton { PrimaryServiceImpl() }

503

bind<FallbackService>() with singleton { FallbackServiceImpl() }

504

}

505

506

fun robustOperation() {

507

try {

508

val service: PrimaryService by instance()

509

service.execute()

510

} catch (e: DI.NotFoundException) {

511

println("Binding not found: ${e.key.fullDescription}")

512

println("Available bindings:")

513

println(e.message) // Contains full container tree due to config

514

515

// Try fallback

516

val fallback: FallbackService by instance()

517

fallback.execute()

518

} catch (e: DI.DependencyLoopException) {

519

println("Dependency loop detected: ${e.message}")

520

// Handle circular dependency

521

}

522

}

523

}

524

525

// Multi-argument and complex binding scenarios

526

val advancedDI = DI {

527

// Multi-argument factory using array

528

bind<ReportGenerator>() with factory { args: Array<Any> ->

529

val format = args[0] as ReportFormat

530

val filters = args[1] as List<DataFilter>

531

val options = args[2] as ReportOptions

532

ReportGeneratorImpl(format, filters, options)

533

}

534

535

// Typed wrapper for preserving generic information

536

bind<Typed<List<User>>>() with singleton {

537

Typed(generic<List<User>>()) {

538

userRepository.getAllUsers()

539

}

540

}

541

}

542

```