vault
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.6.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