Skip to content

First Store Tutorial

This tutorial gives you the smallest useful Maryk loop:

  1. define a model,
  2. create and validate data,
  3. serialize it,
  4. store it,
  5. query it,
  6. read versioned changes.

Add the dependencies to your target source set:

repositories {
mavenCentral()
}
dependencies {
implementation("io.maryk:maryk-core:<maryk-version>")
implementation("io.maryk:maryk-memory:<maryk-version>")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:<coroutines-version>")
}

Every stored model extends RootDataModel. Property indexes are the field numbers in the wire/storage contract. Add new property indexes over time; do not reuse old property indexes for different meanings. Query indexes are separate and only needed for extra access paths.

import maryk.core.models.RootDataModel
import maryk.core.models.key
import maryk.core.properties.definitions.date
import maryk.core.properties.definitions.string
import kotlinx.datetime.LocalDate
object Person : RootDataModel<Person>() {
val firstName by string(index = 1u)
val lastName by string(index = 2u)
val dateOfBirth by date(index = 3u)
}
val jane = Person.create {
firstName with "Jane"
lastName with "Doe"
dateOfBirth with LocalDate(1990, 1, 15)
}
Person.validate(jane)
val json = Person.Serializer.writeJson(jane, pretty = true)
val sameJane = Person.Serializer.readJson(json)

Use the model serializer for JSON directly. The same serializer can also write through JSON-like writers, including YAML. Use ProtoBuf for compact binary transport once both sides already know the model.

import kotlinx.coroutines.runBlocking
import maryk.core.query.requests.add
import maryk.core.query.requests.get
import maryk.core.query.requests.getChanges
import maryk.datastore.memory.InMemoryDataStore
fun main() = runBlocking {
InMemoryDataStore.open(
keepAllVersions = true,
keepUpdateHistoryIndex = true,
dataModelsById = mapOf(1u to Person)
).use {
val key = Person.key(jane)
execute(Person.add(key to jane))
val current = execute(Person.get(key))
val values = current.values.single().values
println(values { firstName }) // Jane
}
}

dataModelsById is the store registry. Keep IDs stable for persistent stores. ID 0u is reserved.

Passing a known key keeps examples and tests easy to read. You can also let the store create keys and inspect add statuses when that better matches your app.

With keepAllVersions = true, Maryk can answer “what changed?” and “what did this look like at version T?”

Inside the same .use block:

val changes = execute(Person.getChanges(key))
val firstVersion = changes.changes.first().changes.first().version
val historic = execute(
Person.get(
key,
toVersion = firstVersion
)
)

Use this for sync, audit views, debugging, and conflict-aware clients.

import kotlinx.coroutines.runBlocking
import maryk.core.models.RootDataModel
import maryk.core.models.key
import maryk.core.properties.definitions.date
import maryk.core.properties.definitions.string
import maryk.core.query.requests.add
import maryk.core.query.requests.get
import maryk.core.query.requests.getChanges
import maryk.datastore.memory.InMemoryDataStore
import kotlinx.datetime.LocalDate
object Person : RootDataModel<Person>() {
val firstName by string(index = 1u)
val lastName by string(index = 2u)
val dateOfBirth by date(index = 3u)
}
fun main() = runBlocking {
val jane = Person.create {
firstName with "Jane"
lastName with "Doe"
dateOfBirth with LocalDate(1990, 1, 15)
}
Person.validate(jane)
val json = Person.Serializer.writeJson(jane, pretty = true)
val sameJane = Person.Serializer.readJson(json)
InMemoryDataStore.open(
keepAllVersions = true,
keepUpdateHistoryIndex = true,
dataModelsById = mapOf(1u to Person)
).use {
val key = Person.key(sameJane)
execute(Person.add(key to sameJane))
val current = execute(Person.get(key))
val values = current.values.single().values
println(values { firstName })
val changes = execute(Person.getChanges(key))
val firstStoredChange = changes.changes.first().changes.first()
val historic = execute(
Person.get(
key,
toVersion = firstStoredChange.version
)
)
println(historic.values.single().values { lastName })
}
}
  • Keep using Memory Store for tests and examples.
  • Use IndexedDB when a JS/WasmJS browser app needs durable local browser storage.
  • Use RocksDB when your app needs embedded persistence.
  • Use FoundationDB when a server deployment needs distributed ACID storage.
  • Use Remote Store when tools, the CLI, or the App need to connect over a network boundary.