capri: Type-safe, atomic Gleam bindings for Khepri, the modern distributed database for the BEAM

Lobsters Hottest Tools

Summary

Capri v0.2.0 offers type-safe, atomic Gleam bindings for the Khepri distributed database, enabling safe data manipulation with repositories, migrations, and projections in the BEAM ecosystem.

<p><a href="https://lobste.rs/s/3tbpsd/capri_type_safe_atomic_gleam_bindings_for">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 08/19/26, 06:33 PM

# capri · v0.2.0 Source: [https://capri.hexdocs.pm/](https://capri.hexdocs.pm/) Type\-safe, atomic Gleam bindings for Khepri\. See the generated module documentation for the complete API reference\. This README focuses on the core concepts and safety model\. ## Getting Started Initialize Khepri and create a`StoreHandle`with`capri\.init/2`\. You will use this handle for accessing and manipulating your Khepri store/cluster\. ``` import capri import gleam/erlang/atom let assert Ok(store) = capri.init(atom.create("my_app"), "./data") ``` ## Repositories Repositories represent the persistence layer for terms, as defined in traditional Domain\-Driven Design\. > This module doesn’t use the Active Record pattern, since objects don’t exist in Gleam, rendering it impossible to create “active” Records\. ### Usage Use`capri\.repository/3`to define a repository, then use`capri\.bind/3`to open or create it for a given store: ``` import capri import capri/path import capri/types import gleam/dynamic/decode import gleam/option.{Some} let assert Ok(users) = capri.repository( "users", 1, types.Decoder(user_decoder), ) let users_path = path.from_key(types.String("users")) let assert Ok(users_prefix) = capri.bind(store, users_path, users) let user_path = capri.child(users_prefix, types.String("user-123")) let assert Ok(Nil) = capri.put(store, user_path, user) let assert Ok(Some(stored_user)) = capri.get(store, user_path) ``` Binding validates that the repository identity and version match the metadata stored at the prefix\. The resulting typed prefix and child paths carry the repository capability used to decode and validate records\. ## Migrations Migrations are atomic, versioned, and reversible transformations of data & schema\. Each migration includes the decoder for its target version, so schemas change in lockstep\. Every step must increment the repository version by exactly one\. > While unsafe \(irreversible\) migrations can be created, you are strongly advised to avoid them\. ``` import capri import capri/types let assert Ok(users_v1) = capri.repository( "users", 1, types.Decoder(user_v1_decoder), ) let assert Ok(users_v2) = capri.add_migration( users_v1, 2, types.Decoder(user_v2_decoder), fn(entry) { let types.Entry(key, user_v1) = entry Ok(types.Entry(key, migrate_user_to_v2(user_v1))) }, fn(entry) { let types.Entry(key, user_v2) = entry Ok(types.Entry(key, restore_user_v1(user_v2))) }, ) let assert Ok(Nil) = capri.migrate_to_current(store, users_path, users_v2) ``` ## Projections Projections are typed, derived views maintained in local ETS tables by Khepri\. They are ephemeral caches rather than authoritative repository state\. ### Set projections The default projection type\. Use`projection\.get/2`to fetch a sole value from a set projection: ``` import capri/projection import gleam/erlang/atom import gleam/option.{Some} let assert Ok(by_email) = users |> projection.new(fn(user) { user.email }) |> projection.named(atom.create("users_by_email")) let assert Ok(Nil) = projection.register(store, users_prefix, by_email) let assert Ok(Some(user)) = projection.get(by_email, "[email protected]") ``` ### Bag projections Use`projection\.many/1`to create a bag projection that allows multiple records to share a key\. Use`projection\.all/2`to query for values\. ``` import capri/projection import gleam/erlang/atom let assert Ok(by_team) = users |> projection.new(fn(user) { user.team_id }) |> projection.many |> projection.named(atom.create("users_by_team")) let assert Ok(Nil) = projection.register(store, users_prefix, by_team) let assert Ok(team_members) = projection.all(by_team, team_id) ``` ## Clustering To join a cluster, use`capri\.reset\_and\_join\_cluster/2`\. > `capri\.reset\_and\_join\_cluster/2`and`capri\.reset\_local\_member/1`erase local data\. ## Naming Khepri sounds phonetically similar to Capri\. Capris are a type of pants, making Capri “pants” for Khepri\. ## Copyright Capri: Type\-safe atomic Gleam bindings for Khepri\. Copyright \(C\) 2026 Software Freedom Conservancy, et al\. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or \(at your option\) any later version\. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\. See the GNU Affero General Public License for more details\. You should have received a copy of the GNU Affero General Public License along with this program\. If not, see[https://www\.gnu\.org/licenses/](https://www.gnu.org/licenses/)\.

Similar Articles

Gleam v1.17.0

Hacker News Top

Gleam v1.17.0 introduces the `gleam export escript` command for creating single-file BEAM programs, highlight references in the language server, and constant `todo` expressions. The first Gleam Gathering conference videos are also released.