Indexes & Hooks

Define indexes with the same type-safe property paths you use in queries. Hook into entity lifecycle for timestamps, validation, and side effects.

Defining indexes

Override buildIndexes() in your repository. Index fields use the same KSP-generated property paths as queries.

class PersonsRepo(driver: MonkoDriver) : MonkoRepository<Person>(
    name = "persons",
    storedType = kType(),
    driver = driver,
) {
    override fun MonkoIndexBuilder<Person>.buildIndexes() {
        // Single field index
        persistentIndex {
            field { it.email }
        }

        // Compound index
        persistentIndex {
            field { it.address.city }
            field { it.address.zip }
        }

        // Unique index with custom name
        uniqueIndex {
            name("idx_email_unique")
            field { it.email }
        }

        // TTL index — auto-delete expired documents
        ttlIndex {
            field { it.expiresAt }
            expireAfter(0)  // delete when expiresAt is in the past
        }

        // Sparse index — only index documents where the field exists
        sparseIndex {
            field { it.middleName }
        }
    }
}

Index types

Builder MongoDB Type Use case
persistentIndex Standard index Query optimization
uniqueIndex Unique index Enforce uniqueness
ttlIndex TTL index Auto-expire documents
sparseIndex Sparse index Index only non-null fields

Index management

// Ensure indexes exist (create if missing, skip if present)
repo.ensureIndexes()

// Drop and recreate all indexes
repo.recreateIndexes()

// Validate without creating
val info = repo.validateIndexes()
On application startup, Funktor calls ensureIndexes() automatically via the EnsureRepositoriesOnAppStarting lifecycle hook. You don't need to manage this manually.

Lifecycle hooks

Hooks run during entity lifecycle events. They use the same Vault hook interfaces as Karango.

Timestamped

The most common hook. Automatically sets createdAt and updatedAt.

@Vault
data class Article(
    val title: String,
    val body: String,
    override val createdAt: MpInstant = MpInstant.Epoch,
    override val updatedAt: MpInstant = MpInstant.Epoch,
) : Timestamped {
    override fun withCreatedAt(instant: MpInstant) = copy(createdAt = instant)
    override fun withUpdatedAt(instant: MpInstant) = copy(updatedAt = instant)
}

Custom hooks

class ArticlesRepo(driver: MonkoDriver) : MonkoRepository<Article>(
    name = "articles",
    storedType = kType(),
    driver = driver,
    hooks = Hooks.of(
        onBeforeSave = listOf(TimestampedOnBeforeSaveHook()),
        onAfterSave = listOf(MyAfterSaveHook()),
        onAfterDelete = listOf(MyAfterDeleteHook()),
    ),
)

Hook execution order

Operation Hook sequence
insert() OnBeforeSave → persist → OnAfterSave
save() OnBeforeSave → persist → OnAfterSave
remove() delete → OnAfterDelete