# Capri

Type-safe atomic Gleam bindings for Khepri.

## Getting Started

Initialize Khepri and create a `StoreHandle` with `lifecycle.bind/2`. You will use this handle for accessing and manipulating your Khepri store/cluster.

```gleam
import capri/lifecycle
import gleam/erlang/atom

let assert Ok(store) =
  lifecycle.bind(atom.create("my_app"), "./data")
```

Then follow the rest of this README to get an overview of how to utilize the library.

## Repository

Repositories represent the persistence layer for terms, as defined in traditional Domain-Driven Design.

> [!TIP]
> 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 `repository.bind/3` to open or create a repository for a given store:

```gleam
let assert Ok(users) =
  repository.new("users", 1, user_decoder, [])

let users_path = path.from_key(path.String("users"))
let assert Ok(prefix) = repository.bind(store, users_path, users)

let user_path = repository.child(prefix, path.String("user-123"))
let assert Ok(Nil) = repository.put(store, user_path, user)
```

Available operations can be found at the package API docs.

## Migrations

Repository migrations are atomic, reversible, and versioned transformations over values & structure of repositories.

> [!WARNING]
>
> While 'unsafe' migrations (migrations that are not reversible) can be made, you are strongly advised to avoid them.

### Usage

```gleam
let assert Ok(one_to_two) =
  repository.migration(
    1,
    2,
    fn(entries) {
      // Transform the complete keyed repository dataset.
      list.map(entries, v1_to_v2)
    },
    fn(entries) {
      list.map(entries, v2_to_v1)
    },
  )

let assert Ok(users) =
  repository.new(
    "users",
    2,
    user_decoder,
    [one_to_two], // migration chain
  )

let assert Ok(Nil) =
  repository.migrate_all(store, users_path, users) // run migration chain
```

## Projections

Projections are typed, derived views maintained by Khepri in local ETS tables. They are ephemeral caches, not authoritative repository state.

### Usage

```gleam
import capri/projection

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, prefix, by_email)

let assert Ok(user) =
  projection.get(by_email, "reese@example.com")
```

Use `projection.many` for one-to-many lookups:

```gleam
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, prefix, by_team)

let assert Ok(team) =
  projection.all(by_team, team_id)
```

Projection keys retain their Gleam type. Projection names are bounded atoms and should not be created from unbounded or user-controlled input.

Use `projection.unregister` to remove a projection and `projection.rebuild` to rebuild it from the repository.

## Lifecycle

Use `lifecycle.bind/2` to initialize Khepri, and get a `StoreHandle`. To connect to a cluster, use `lifecycle.reset_and_join_cluster/2`.

> [!WARNING]
> `lifecycle.reset_and_join_cluster/2` and `lifecycle.reset_local_member/1` erase local data.

## Naming

Khepri sounds phonetically similar to capri, and capri, capris being a type of pants, is like "pants" for Khepri :P.

## 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/>.
