Simple persistence layer for kotlin objects
Find a file
2026-02-09 21:39:01 +01:00
gradle/wrapper chore(gradle/ci): bump gradle version 2026-02-09 20:48:38 +01:00
src fix(FsStoreDriver): automatically create backingPath directory if missing 2026-02-09 21:34:28 +01:00
test init 2025-11-14 23:13:43 +01:00
.gitignore init 2025-11-14 23:13:43 +01:00
.woodpecker.yaml fix(ci): there was a fucking dollar missing 2026-02-09 21:07:32 +01:00
build.gradle.kts chore(gradle/ci): bump gradle version 2026-02-09 20:48:38 +01:00
gradle.properties init 2025-11-14 23:13:43 +01:00
gradlew init 2025-11-14 23:13:43 +01:00
gradlew.bat init 2025-11-14 23:13:43 +01:00
LICENSE readme/license 2025-11-14 23:38:55 +01:00
README.md docs(README): add gradle setup instructions 2026-02-09 21:39:01 +01:00
settings.gradle.kts init 2025-11-14 23:13:43 +01:00

Store

status-badge

A simple persistence layer for arbitrary, typed and untyped kotlin data. It utilizes kotlinx-serialization and a flexible driver system.

Usage

Repository setup

repositories {
    maven("https://git.bossing.vip/api/packages/max/maven") 
}

dependencies {
    implementation("ng.bossi:Store:1.0.1")
}

Typed data

// Define some data
@Serializable
data class User(val id: Int, val name: String, val age: Int)

val store = typedStore<User>(
    // Use FsStoreDriver to save data to disk
    driver = FsStoreDriver(Path("./store")),
    // Set a namespace to allow for segmentation while using the same driver
    namespace = "typed:",
    // Set your preferred serialization format
    format = Json
)

// Persist your first data

val bob = User(id = 1, name = "bob", age = 30)

store["user.${user.id}"] = bob // thats it!

// access the data again
println(store["user.${user.id}"]!!.name) // -> "bob" 

// check when it was last accessed
println(store.age("${user.id}")) // -> A epoch timestamp pointing to the last operation on Bob

// remove data
store -= "user.${user.id}"

// and clear all of it 
store.clear()

Untyped data

val store = untypedStore(
    // MemStoreDriver will not persist or write the data and simply hold it in memory until the store is GC-ed
    driver = MemStoreDriver(),
    // You can still segment it tho
    namespace = "untyped:",
    // And use another of your favourite data formats, thanks to ktx.serialization!
    format = Toml,
)

// Persist some random data you have
store<String>["test.string"] = "Hello, World!"
store<Boolean>["test.bool"] = true

println(store<Boolean>["test.bool"]) // -> "true"
println(store<Boolean>["test.string"]) // this will fail!

// This also works with more complex elements as long as they are @Serializable
@Serializable
data class Person(val name: String)

@Serializable 
data class Dog(val type: String, val name: String, val owner: Person)

store<Dog>["dog.stella"] = Dog("corgy", "Stella", Person("Dave"))

Defining your own Driver

A driver is a simple interface that handles the actual storage of the data that is being persisted. Drivers do not concern themselves with what data they get, simply with how they will store and access it


class ConcurrentMemDriver : StoreDriver {
    private val map = ConcurrentHashMap<String, Pair<Long, String>>()
    
    fun has(key: String): Boolean = map.contains(key)
    fun remove(key: String): Boolean = map.remove(key) != null
    fun age(key: String): Long? = map[key]?.first
    fun get(key: String): String? = map[key]?.second
    fun set(key: String, value: String) = map.set(key, value)
    fun all(): Set<String> = map.keys
}

License

[MIT], go nuts