vault

Maven Central

Project Goals

Vault is a tiny library that provides a single data structure called vault.

Inspiration was drawn from HeinrichApfelmus/vault and the original blog post

A vault is a type-safe, persistent storage for values of arbitrary types. Like Ref, it should be capable of storing values of any type in it, but unlike Ref, behave like a persistent, first-class data structure.

It is analogous to a bank vault, where you can access different bank boxes with different keys; hence the name.

Quick Start

To use vault in an existing SBT project with Scala 2.11 or a later version, add the following dependencies to your build.sbt depending on your needs:

libraryDependencies ++= Seq(
  "org.typelevel" %% "vault" % "3.5.0",
)

First the imports

import cats.effect._
import cats.implicits._
import org.typelevel.vault._

// Importing global cats-effect runtime to allow .unsafeRunSync();
// In real code you should follow cats-effect advice on obtaining a runtime
import cats.effect.unsafe.implicits.global

Then some basic operations

case class Bar(a: String, b: Int, c: Long)

// Creating keys are effects, but interacting with the vault
// not, it acts like a simple persistent store.

val basicLookup = for {
  key <- Key.newKey[IO, Bar]
} yield {
  Vault.empty
    .insert(key, Bar("", 1, 2L))
    .lookup(key)
}
basicLookup.unsafeRunSync()
// res0: Option[Bar] = Some(value = Bar(a = "", b = 1, c = 2L))
val containsKey = for {
  key <- Key.newKey[IO, Bar]
} yield {
  Vault.empty
    .insert(key, Bar("", 1, 2L))
    .contains(key)
}
containsKey.unsafeRunSync()
// res1: Boolean = true
val lookupValuesOfDifferentTypes = for {
  key1 <- Key.newKey[IO, Bar]
  key2 <- Key.newKey[IO, String]
  key3 <- Key.newKey[IO, String]
} yield {
  val myvault = Vault.empty
    .insert(key1, Bar("", 1, 2L))
    .insert(key2, "I'm at Key2")
    .insert(key3, "Key3 Reporting for Duty!")
  
  (myvault.lookup(key1), myvault.lookup(key2), myvault.lookup(key3))
    .mapN((_,_,_))
}
lookupValuesOfDifferentTypes.unsafeRunSync()
// res2: Option[(Bar, String, String)] = Some(
//   value = (
//     Bar(a = "", b = 1, c = 2L),
//     "I'm at Key2",
//     "Key3 Reporting for Duty!"
//   )
// )
val emptyLookup = for {
  key <- Key.newKey[IO, Bar]
} yield {
  Vault.empty
    .lookup(key)
}
emptyLookup.unsafeRunSync()
// res3: Option[Bar] = None
val doubleInsertTakesMostRecent = for {
  key <- Key.newKey[IO, Bar]
} yield {
  Vault.empty
    .insert(key, Bar("", 1, 2L))
    .insert(key, Bar("Monkey", 7, 5L))
    .lookup(key)
}
doubleInsertTakesMostRecent.unsafeRunSync()
// res4: Option[Bar] = Some(value = Bar(a = "Monkey", b = 7, c = 5L))
val mergedVaultsTakesLatter = for {
  key <- Key.newKey[IO, Bar]
} yield {
  (
    Vault.empty.insert(key, Bar("", 1, 2L)) ++
    Vault.empty.insert(key, Bar("Monkey", 7, 5L))
  ).lookup(key)
}
mergedVaultsTakesLatter.unsafeRunSync()
// res5: Option[Bar] = Some(value = Bar(a = "Monkey", b = 7, c = 5L))
val deletedKeyIsMissing = for {
  key <- Key.newKey[IO, Bar]
} yield {
  Vault.empty
    .insert(key, Bar("", 1, 2L))
    .delete(key)
    .lookup(key)
}
deletedKeyIsMissing.unsafeRunSync()
// res6: Option[Bar] = None

We can also interact with a single value locker instead of the larger datastructure that a vault enables.

val lockerExample = for {
  key <- Key.newKey[IO, Bar]
} yield {
  Locker(key, Bar("", 1, 2L))
    .unlock(key)
}
lockerExample.unsafeRunSync()
// res7: Option[Bar] = Some(value = Bar(a = "", b = 1, c = 2L))
val wrongLockerExample = for {
  key <- Key.newKey[IO, Bar]
  key2 <- Key.newKey[IO, Bar]
} yield {
  Locker(key, Bar("", 1, 2L))
    .unlock(key2)
}
wrongLockerExample.unsafeRunSync()
// res8: Option[Bar] = None