Repositories
EntityRepository gives you CRUD, batch operations, custom queries, and cursor streaming — all as
suspend functions.
Defining a repository
@Vault
data class BlogPost(
val title: String,
val content: String,
val author: String,
val published: Boolean = false,
)
class BlogPostsRepo(driver: KarangoDriver) : EntityRepository<BlogPost>(
name = "blog_posts",
storedType = kType(),
driver = driver,
) That's it. You get full CRUD, queries, batch operations, and index management for free.
Insert
val post = BlogPost("Hello World", "My first post", "Alice")
// Insert and get the stored version (with _id, _key, _rev)
val stored: Stored<BlogPost> = repo.insert(post)
println(stored._id) // "blog_posts/abc123"
println(stored._key) // "abc123"
println(stored().title) // "Hello World"
// Insert with explicit key
val stored2 = repo.insert("my-key", post) Find
// By ID
val post: Stored<BlogPost>? = repo.findById("blog_posts/abc123")
// By multiple IDs
val posts: Cursor<Stored<BlogPost>> = repo.findByIds("id1", "id2", "id3")
// All documents
val all: Cursor<Stored<BlogPost>> = repo.findAll()
// Custom query — returns a cursor
val published = repo.find {
FOR(repo) { post ->
FILTER(post.published EQ true)
SORT(post.title.ASC)
RETURN(post)
}
}
// Convenience: returns a List directly
val list = repo.findList {
FOR(repo) { post ->
FILTER(post.author EQ "Alice")
RETURN(post)
}
}
// Convenience: returns the first match
val first = repo.findFirst {
FOR(repo) { post ->
FILTER(post.title EQ "Hello World")
LIMIT(1)
RETURN(post)
}
} Update
// Save a modified entity
val updated = repo.save(stored.modify { it.copy(published = true) })
// Modify by ID — atomic read-modify-write
repo.modifyById(stored._id) { post ->
post.copy(published = true)
}
// Conditional modify — only if the condition holds
// condition receives Stored<T>, modify receives T
repo.modifyByIdWhen(
stored._id,
condition = { !it().published },
modify = { it.copy(published = true) },
) Remove
// By stored entity
repo.remove(stored)
// By ID or key
repo.remove("blog_posts/abc123")
// Remove all documents
repo.removeAll() Batch insert
val posts = listOf(
BlogPost("Post 1", "Content 1", "Alice"),
BlogPost("Post 2", "Content 2", "Bob"),
BlogPost("Post 3", "Content 3", "Alice"),
)
val stored: List<Stored<BlogPost>> = repo.batchInsertValues(posts) Batch insert runs hooks on each entity, just like single inserts.
Cursors
Queries return Cursor<T>, a Flow-based wrapper. Use asFlow() for streaming
or the suspend convenience extensions for common operations:
val cursor = repo.findAll()
cursor.count // number of results in current batch
cursor.fullCount // total count (when using PAGE)
// Collect all results
cursor.toList() // suspend
// Iterate
cursor.forEach { stored -> println(stored().title) } // suspend
// Transform and filter (all suspend)
cursor.map { it().title }
cursor.filter { it().published }
cursor.firstOrNull()
cursor.groupBy { it().author }
// Flow-based streaming
cursor.asFlow().collect { ... } Results are deserialized lazily. Stored<T> entities are automatically cached in the EntityCache
for deduplication. All cursor convenience methods (map, filter,
forEach, toList, etc.) are suspend functions.
The Stored<T> wrapper
Every entity loaded from ArangoDB is wrapped in Stored<T>:
val stored: Stored<BlogPost> = repo.findById(id)!!
stored._id // ArangoDB document ID: "blog_posts/abc123"
stored._key // Document key: "abc123"
stored._rev // Revision: "_abc123"
stored() // The actual BlogPost data class
// Create a modified version
val modified = stored.modify { it.copy(title = "New Title") }
// Save it back
repo.save(modified) Accessing the repository from Database
Use extension properties to access repositories through the Database:
// Define an accessor
val Database.blogPosts get() = getRepository<BlogPostsRepo>()
// Use it
database.blogPosts.findAll()
database.blogPosts.insert(New(post))