Vault (vault v0.2.1)

View Source

Vault is a lightweight Elixir library for immutable data storage within a process subtree.

Due to Elixir's actor model nature, it's common for a process to have global context that is valid for every function call inside of the process and its children.

For example, this context can include:

  • A user when processing a user's request
  • A tenant in a multi-tenant application
  • Rate limiting buckets/quotas
  • Cache namespaces
  • API or client versions
  • And many more, depending on your application domain

Vault.init/1 provides you a guarantee that the context can only be defined once per existing process subtree, so you won't override it by accident. This makes it easy to reason about your context origination.

Usage

# Initialize vault in parent process
Vault.init(current_user: %{id: 1, first_name: "Alice", role: "admin"})

# Access data from any descendant process, even these not linked!
spawn(fn ->
  Vault.get(:current_user) # => %{id: 1, first_name: "Alice", role: "admin"}

  Vault.init(current_user: :user) # => raises, because the ancestor already has vault initialized
end)

# Access data from the parent process itself
Vault.get(:current_user) # => %{id: 1, first_name: "Alice", role: "admin"}

However, if for some reason you need to split initializations, you can use Vault.unsafe_merge/1, but the immutability is no longer guaranteed.

Why Vault?

  • Instead of property-drilling context data through every function call, Vault provides access to shared data across your process tree. When used for immutable data - this is a cleaner and more maintainable approach, simplifying cognitive load when reasoning about your code.
  • The data is initialized only once and is immutable (unless you explicitly call unsafe_* functions).
  • The API mirrors Elixir's Map module for familiar data access.

Summary

Functions

Extracts the value from the current process vault OR from the nearest ancestor process vault (if any).

Extracts the value from the current process vault OR from the nearest ancestor process vault (if any).

Extracts the value from the current process vault OR from the nearest ancestor process vault (if any).

Extracts the value from the current process vault OR from the nearest ancestor process vault (if any).

Checks if the key exists in the current process vault OR in the nearest ancestor process vault (if any).

Initializes Vault in the current process with the given data.

Returns the keys from the current process vault OR from the nearest ancestor process (if any).

Extracts the specified keys and their values from the current process vault OR from the nearest ancestor process vault (if any).

Returns the vault as a keyword list from the current process OR from the nearest ancestor process (if any).

Merges the given data into the current process vault.

Puts the given key-value pair into the current process vault.

Updates the value for the given key in the current process vault.

Returns the values from the current process vault OR from the nearest ancestor process (if any).

Returns the vault map for the current process or its nearest ancestor.

Functions

fetch(key)

Extracts the value from the current process vault OR from the nearest ancestor process vault (if any).

If vault is found in an ancestor process, it's cached in the current process dictionary for faster subsequent access.

Examples

iex> Vault.init(current_user: "diana")
:ok
iex> Vault.fetch(:current_user)
{:ok, "diana"}

iex> Vault.fetch(:missing_key)
:error

See Map.fetch/2 for details.

fetch!(key)

Extracts the value from the current process vault OR from the nearest ancestor process vault (if any).

If vault is found in an ancestor process, it's cached in the current process dictionary for faster subsequent access.

Examples

iex> Vault.init(current_user: "charlie")
:ok
iex> Vault.fetch!(:current_user)
"charlie"

See Map.fetch!/2 for details.

get(key, default \\ nil)

Extracts the value from the current process vault OR from the nearest ancestor process vault (if any).

If vault is found in an ancestor process, it's cached in the current process dictionary for faster subsequent access.

Examples

iex> Vault.init(current_user: "eve")
:ok
iex> Vault.get(:current_user)
"eve"

iex> Vault.get(:missing_key, "default")
"default"

See Map.get/3 for details.

get_lazy(key, fun)

Extracts the value from the current process vault OR from the nearest ancestor process vault (if any).

If vault is found in an ancestor process, it's cached in the current process dictionary for faster subsequent access.

Examples

iex> Vault.init(current_user: "frank")
:ok
iex> Vault.get_lazy(:current_user, fn -> "default" end)
"frank"

iex> Vault.get_lazy(:missing_key, fn -> "lazy_default" end)
"lazy_default"

See Map.get_lazy/3 for details.

has_key?(key)

Checks if the key exists in the current process vault OR in the nearest ancestor process vault (if any).

If vault is found in an ancestor process, it's cached in the current process dictionary for faster subsequent access.

Examples

iex> Vault.init(current_user: "grace")
:ok
iex> Vault.has_key?(:current_user)
true

iex> Vault.has_key?(:missing_key)
false

See Map.has_key?/2 for details.

init(initial_data)

Initializes Vault in the current process with the given data.

Examples

iex> Vault.init(current_user: "alice")
:ok
iex> Vault.get(:current_user)
"alice"

keys()

Returns the keys from the current process vault OR from the nearest ancestor process (if any).

If vault is found in an ancestor process, it's cached in the current process dictionary for faster subsequent access.

Examples

iex> Vault.init(current_user: "henry", role: "admin")
:ok
iex> Vault.keys() |> Enum.sort()
[:current_user, :role]

See Map.keys/1 for details.

take(keys)

Extracts the specified keys and their values from the current process vault OR from the nearest ancestor process vault (if any).

If vault is found in an ancestor process, it's cached in the current process dictionary for faster subsequent access.

Examples

iex> Vault.init(current_user: "iris", role: "admin", team: "ops")
:ok
iex> Vault.take([:current_user, :role])
%{current_user: "iris", role: "admin"}

See Map.take/2 for details.

to_list()

Returns the vault as a keyword list from the current process OR from the nearest ancestor process (if any).

If vault is found in an ancestor process, it's cached in the current process dictionary for faster subsequent access.

Examples

iex> Vault.init(current_user: "jack")
:ok
iex> Vault.to_list()
[current_user: "jack"]

See Map.to_list/1 for details.

unsafe_merge(new_data)

Merges the given data into the current process vault.

Examples

iex> Vault.init(current_user: "nick")
:ok
iex> Vault.unsafe_merge(role: "admin", team: "engineering")
:ok
iex> Vault.get(:current_user)
"nick"
iex> Vault.get(:role)
"admin"

unsafe_put(key, value)

Puts the given key-value pair into the current process vault.

Examples

iex> Vault.init(current_user: "leo")
:ok
iex> Vault.unsafe_put(:current_user, "updated_leo")
:ok
iex> Vault.get(:current_user)
"updated_leo"

The updates won't be propagated to processes that already have a vault initialized.

unsafe_update(key, default, fun)

Updates the value for the given key in the current process vault.

Examples

iex> Vault.init(current_user: "mia")
:ok
iex> Vault.unsafe_update(:current_user, "default", fn user -> String.upcase(user) end)
:ok
iex> Vault.get(:current_user)
"MIA"

values()

Returns the values from the current process vault OR from the nearest ancestor process (if any).

If vault is found in an ancestor process, it's cached in the current process dictionary for faster subsequent access.

Examples

iex> Vault.init(current_user: "kate", role: "user")
:ok
iex> Vault.values() |> Enum.sort()
["kate", "user"]

See Map.values/1 for details.

vault(opts \\ [])

Returns the vault map for the current process or its nearest ancestor.

Examples

iex> Vault.init(current_user: "bob")
:ok
iex> Vault.vault()
%{current_user: "bob"}