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" % "<version>",
)

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: IO[Option[Bar]] = Map(
//   Map(
//     Delay(cats.effect.kernel.Sync$$Lambda$7226/1855292553@4c912b0c),
//     org.typelevel.vault.Key$$$Lambda$7227/1932724422@6235821a
//   ),
//   <function1>
// )

basicLookup.unsafeRunSync
// res0: Option[Bar] = Some(Bar("", 1, 2L))

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: IO[Option[(Bar, String, String)]] = FlatMap(
//   Map(
//     Delay(cats.effect.kernel.Sync$$Lambda$7226/1855292553@4c912b0c),
//     org.typelevel.vault.Key$$$Lambda$7227/1932724422@6235821a
//   ),
//   <function1>
// )

lookupValuesOfDifferentTypes.unsafeRunSync
// res1: Option[(Bar, String, String)] = Some(
//   (Bar("", 1, 2L), "I'm at Key2", "Key3 Reporting for Duty!")
// )

val emptyLookup = for {
  key <- Key.newKey[IO, Bar]
} yield {
  Vault.empty
    .lookup(key)
}
// emptyLookup: IO[Option[Bar]] = Map(
//   Map(
//     Delay(cats.effect.kernel.Sync$$Lambda$7226/1855292553@4c912b0c),
//     org.typelevel.vault.Key$$$Lambda$7227/1932724422@6235821a
//   ),
//   <function1>
// )

emptyLookup.unsafeRunSync
// res2: 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: IO[Option[Bar]] = Map(
//   Map(
//     Delay(cats.effect.kernel.Sync$$Lambda$7226/1855292553@4c912b0c),
//     org.typelevel.vault.Key$$$Lambda$7227/1932724422@6235821a
//   ),
//   <function1>
// )

doubleInsertTakesMostRecent.unsafeRunSync
// res3: Option[Bar] = Some(Bar("Monkey", 7, 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: IO[Option[Bar]] = Map(
//   Map(
//     Delay(cats.effect.kernel.Sync$$Lambda$7226/1855292553@4c912b0c),
//     org.typelevel.vault.Key$$$Lambda$7227/1932724422@6235821a
//   ),
//   <function1>
// )

mergedVaultsTakesLatter.unsafeRunSync
// res4: Option[Bar] = Some(Bar("Monkey", 7, 5L))

val deletedKeyIsMissing = for {
  key <- Key.newKey[IO, Bar]
} yield {
  Vault.empty
    .insert(key, Bar("", 1, 2L))
    .delete(key)
    .lookup(key)
}
// deletedKeyIsMissing: IO[Option[Bar]] = Map(
//   Map(
//     Delay(cats.effect.kernel.Sync$$Lambda$7226/1855292553@4c912b0c),
//     org.typelevel.vault.Key$$$Lambda$7227/1932724422@6235821a
//   ),
//   <function1>
// )

deletedKeyIsMissing.unsafeRunSync
// res5: 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.lock(key, Bar("", 1, 2L))
    .unlock(key)
}
// lockerExample: IO[Option[Bar]] = Map(
//   Map(
//     Delay(cats.effect.kernel.Sync$$Lambda$7226/1855292553@4c912b0c),
//     org.typelevel.vault.Key$$$Lambda$7227/1932724422@6235821a
//   ),
//   <function1>
// )

lockerExample.unsafeRunSync
// res6: Option[Bar] = Some(Bar("", 1, 2L))

val wrongLockerExample = for {
  key <- Key.newKey[IO, Bar]
  key2 <- Key.newKey[IO, Bar]
} yield {
  Locker.lock(key, Bar("", 1, 2L))
    .unlock(key2)
}
// wrongLockerExample: IO[Option[Bar]] = FlatMap(
//   Map(
//     Delay(cats.effect.kernel.Sync$$Lambda$7226/1855292553@4c912b0c),
//     org.typelevel.vault.Key$$$Lambda$7227/1932724422@6235821a
//   ),
//   <function1>
// )

wrongLockerExample.unsafeRunSync
// res7: Option[Bar] = None