a database layer insipred by caqti and ecto
README.md

repodb#

An Ecto-inspired database toolkit for OCaml.

repodb brings the elegance of Elixir's Ecto to OCaml: type-safe queries, composable changesets, and clean separation between your domain and the database.

Features#

  • Schemas - Define your data structures with typed fields
  • Changesets - Validate and track changes before persisting
  • Query DSL - Build type-safe queries with a composable API
  • Type-Safe INSERTs - Compile-time verification that column types match value types
  • Repo - Clean abstraction over database operations
  • Associations - Define relationships (has_many, belongs_to, many_to_many)
  • Preloading - Efficiently load associations, avoiding N+1 queries
  • Multi - Compose multiple operations in a single transaction
  • Migrations - Version your database schema
  • Connection Pool - Scheduler-agnostic, lock-free connection pooling

Quick Example#

(* Define a schema *)
let users_table = Schema.table "users"

type user = { id : int; name : string; email : string; age : int }

let name_field =
  Field.make ~table_name:"users" ~name:"name" ~ty:Types.string
    ~get:(fun u -> u.name) ~set:(fun v u -> { u with name = v }) ()

let age_field =
  Field.make ~table_name:"users" ~name:"age" ~ty:Types.int
    ~get:(fun u -> u.age) ~set:(fun v u -> { u with age = v }) ()

(* Create and validate a changeset *)
let empty_user = { id = 0; name = ""; email = ""; age = 0 }

let changeset =
  Changeset.create empty_user
  |> Changeset.cast [("name", "Alice"); ("email", "alice@example.com")]
       ~fields:[name_field; email_field]
  |> Changeset.validate_required [name_field; email_field]
  |> Changeset.validate_format email_field ~pattern:"^[^@]+@[^@]+$"

(* Build type-safe queries using field definitions *)
let query =
  Query.(from users_table
    |> where Expr.(column age_field > int 18)
    |> order_by ~direction:Desc (Expr.column created_at_field)
    |> limit 10)

let users = Repo.all_query conn query ~decode:decode_user

(* Type-safe insert - compiler verifies column/value types match *)
let insert_query =
  Query.insert_into users_table
  |> Query_values.values3
      (name_field, email_field, age_field)
      (Expr.string "Alice", Expr.string "alice@example.com", Expr.int 25)

Documentation#

Packages#

Package Description
repodb Core library with schemas, changesets, queries
repodb-sqlite SQLite driver
repodb-postgresql PostgreSQL driver

Installation#

opam install repodb repodb-sqlite   # or repodb-postgresql

Philosophy#

repodb follows Ecto's philosophy:

  1. Explicit over implicit - No magic, every operation is visible
  2. Composable - Small functions that combine well
  3. Separation of concerns - Domain logic separate from persistence
  4. Fail fast - Validate early with changesets

License#

ISC License