Fast NoSql document store for Kotlin Multiplatform, inspired by Nitrite-java and MongoDB.
With support for typed and schemaless data, lets you work with JSON objects easily, leveraging kotlin.serialization
for fast and simple object serialization/deserialization.
Originally created for JetBrains/package-search-intellij-plugin for a fast and reliable offline cache, evolved for all KMP developers 🚀
Some key highlights:
- Multiplatform: Works on Kotlin/JVM, Kotlin/JS, and ALL Kotlin/Native platforms (excepts wasm).
- Typed and Schemaless Storage: Use strongly-typed data models using kotlin.serialization or raw JSON depending on your needs.
- Simple APIs: Built with a developer-friendly, coroutine-based API.
- Indexing Support: Create and manage indexes for efficient querying of data.
- Thread-Safe and Asynchronous: Built to handle concurrent read/write operations safely, with coroutine-based APIs for non-blocking interactions.
- Extensible: Easily extend functionality by plugging in custom serializers or storage backends.
Whether you're building desktop, web, or backend applications, kotlin.document.store
provides a unified, intuitive way to manage structured or unstructured data across platforms.
There are three main implementations of the DataStore
interface:
- LevelDB: For all Kotlin platforms (excluding JS and Wasm), using kotlin-leveldb key-value store.
- JVM:
- Windows: arm64, x64
- Linux: arm64, x64
- macOs: arm64, x64
- JS
- Native (Linux, macOS, Windows, iOS, Android, Android native, watchOS, tvOS)
- JVM:
- Browser: For browser-based applications, using the browser's IndexedDB storage.
- JS
- MVStore: For JVM-based applications, using the H2 Database Engine MVStore. Recommended only for IntelliJ Plugin development.
- JVM
google/leveldb is licensed under BSD-3-Clause license, all rights reserved to the original authors.
The modules core
and test
are common to all platforms and contain the main interfaces and tests for the library and they support also wasmWasi
.
Import the library to your project, see the latest version in the Releases page:
dependencies {
implementation("com.github.lamba92:kotlin-document-store-leveldb:{latest_version}")
}
Alternatively with the provided version catalog:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
versionCatalogs {
create("kotlinDocumentStore") {
from("com.github.lamba92:kotlin-document-store-version-catalog:{latest_version}")
}
}
}
// build.gradle.kts
dependencies {
implementation(kotlinDocumentStore.leveldb)
}
class MyActivity : CompactActivity() {
override fun onCreate(): String {
val store = context.getLevelDBStore()
val db = KotlinDocumentStore(store)
// your stuff...
}
}
fun main() {
val store = LevelDBStore.open("path/to/db")
val db = KotlinDocumentStore(store)
// your stuff...
}
Import the library to your project, see the latest version in the Releases page:
dependencies {
implementation("com.github.lamba92:kotlin-document-store-browser:{latest_version}")
}
Alternatively with the provided version catalog:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
versionCatalogs {
create("kotlinDocumentStore") {
from("com.github.lamba92:kotlin-document-store-version-catalog:{latest_version}")
}
}
}
// build.gradle.kts
dependencies {
implementation(kotlinDocumentStore.browser)
}
val db = KotlinDocumentStore(BrowserStore)
Use ObjectCollection
to store and manipulate strongly typed objects:
@Serializable
data class User(
// id declaration is optional, but if declared make sure
// to make it `Long? = null` to handle insertions where id is not provided
@SerialName("_id") val id: Long? = null,
val name: String,
val age: Int
)
// ...
val documentStore = KotlinDocumentStore(aDataStore)
// Retrieve a typed collection
val userCollection = documentStore.getObjectCollection<User>("users")
// Insert a user
val user = userCollection.insert(User(name = "John Smith", age = 30))
println("Inserted User: $user") // will also print the generated id
val id = requireNotNull(user.id) { "IMPOSSIBAH!" }
println("User ID: $id")
// Query by name
val queriedUser = userCollection.find("name", "John Smith").firstOrNull()
println("Queried User: $queriedUser")
// Close the store
documentStore.close()
Use JsonCollection
to store and manipulate raw JSON objects:
val documentStore = KotlinDocumentStore(aDataStore)
// Create or fetch a collection
val collection = documentStore.getJsonCollection("users")
// Insert a document
val json = buildJsonObject { // kotlinx.serialization json APIs
put("name", "Jane Doe")
put("age", 25)
}
collection.insert(json)
// Retrieve the inserted document
val allUsers = collection.iterateAll().toList()
println("Users: $allUsers")
// Clean up
documentStore.close()
Any collection, JSON or typed, can have indexes created on them for faster querying:
val documentStore = kotlinDocumentStore(aDataStore)
val collection = documentStore.getJsonCollection("users")
// Create an index
collection.createIndex("name")
// Insert documents
val json1 = buildJsonObject {
put("name", "Alice")
put("age", 28)
}
val json2 = buildJsonObject {
put("name", "Bob")
put("age", 34)
}
collection.insert(json1)
collection.insert(json2)
// Use the index to query
val results = collection.find("name", "Alice").toList()
println("Search Results: $results")
In the example above, we create an index on the name
field of the users
collection. We then insert two documents into the collection and query for documents where the name
field is Alice
. The query uses the index to find the documents faster.
Indexes can be created using JSON selectors to index nested fields. For example, consider the following JSON document:
{
"name": "Alice",
"address": {
"city": "New York",
"zip": 10001
}
}
To create an index on the city
field in the address
object, we can use a JSON selector:
val documentStore = kotlinDocumentStore(aDataStore)
val collection = documentStore.getJsonCollection("users")
collection.createIndex("address.city")
Now, an index is created on the city
field in the address
object. We can query for documents where the city
field is, for example, New York
:
val results = collection.find("address.city", "New York").toList()
Indexes can also be created on array fields. For example, consider the following JSON document:
{
"name": "Alice",
"tags": ["tag1", "tag2", "tag3"]
}
To create an index on the tags
array field, we can use a JSON selector:
val documentStore = kotlinDocumentStore(aDataStore)
val collection = documentStore.getJsonCollection("users")
collection.createIndex("tags.$3")
Now, an index is created on the third element of the tags
array field. We can query for documents where the third element of the tags
array is, for example, tag3
:
val results = collection.find("tags.$3", "tag3").toList()
The ID field name is _id
and cannot be changed in the representation inside the database. The ID has to be of type Long
and is autogenerated if not provided when inserting a document.
When using typed collections, the ID field is optional in the data class, but it has to be of type Long?
and nullable. Dor example:
@Serializable
data class User(
val _id: Long? = null,
val name: String,
val age: Int
)
It is possible to change the name of the ID field in the data class using the @SerialName
annotation, but the actual field name in the database will always be _id
:
@Serializable
data class User(
@SerialName("_id") val id: Long? = null,
val name: String,
val age: Int
)
To test your own implementation of DataStore,
you can use the provided module kotlin-document-store-test
:
// build.gradle.kts
// Kotlin/JVM
dependencies {
testImplementation("com.github.lamba92:kotlin-document-store-test:{latest_version}")
}
// Kotlin/JS or Kotlin/Multiplatform
kotlin {
sourceSets {
commonTest {
dependencies {
implementation("com.github.lamba92:kotlin-document-store-test:{latest_version}")
}
}
}
}
Classes of tests are provided and only the implementation of DataStore
is needed to run them. See test implementation for: