March Docs

Depot

  • Depot — database library for March.

-

  • Depot is a composable ORM toolkit for March applications. It covers the
  • full lifecycle of working with relational data: defining schemas, building
  • queries, validating input, running migrations, and inspecting query plans.

-

  • ## Sub-modules

-

-ModuleResponsibility
-Depot.SchemaDefine table schemas — field names, types, and associations
-Depot.QueryComposable query builder (from / where_eq / exec_sql)
-Depot.RepoIn-memory repository for testing (insert/get/all/delete)
-Depot.GateChangeset-style validation and casting (cast + validate)
-Depot.MigrationSchema migrations — DDL generation with up/down versioning
-Depot.TransactionDatabase transactions with automatic savepoint nesting
-Depot.TypeCustom type adapters (Email, Enum) for field coercion
-Depot.EmbedEmbedded schemas — nested assoc-list records inside a row
-Depot.ExplainEXPLAIN / EXPLAIN ANALYZE query-plan inspection
-Depot.UrlParse database URLs (postgres://, sqlite:) into targets
-Depot.DbRepoNamed repo registry — start, migrate, query, transaction

-

  • ## Quick start

-

  • ### 1. Define a schema

-

  • fn user_schema() do
  • Depot.Schema.define("users", {
  • fields = {
  • name = "String",
  • email = "String",
  • age = ("Int", { default: 0 })
  • }
  • })
  • end

-

  • ### 2. Validate input with Gate

-

  • fn user_gate(params) do
  • Depot.Gate.cast(params, ["name", "email", "age"])
  • |> Depot.Gate.validate_required(["name", "email"])
  • |> Depot.Gate.validate_format("email", "@")
  • |> Depot.Gate.validate_number("age", [NumMin(13)])
  • end

-

  • ### 3. Persist with Repo

-

  • let gate = user_gate(conn_params)
  • match Depot.Repo.insert(user_schema(), gate) do
  • Ok(record) -> record
  • Err(gate) -> Depot.Gate.errors(gate)
  • end

-

  • ### 4. Query

-

  • let rows =
  • Depot.Query.from(user_schema())
  • |> Depot.Query.where_gt("age", "18")
  • |> Depot.Query.order_asc("name")
  • |> Depot.Query.limit(20)
  • |> Depot.Query.exec_sql(db_conn)

-

  • ### 5. Named repos with Depot.DbRepo (Ecto-style)

-

  • Start a repo from a URL (or an environment variable) once at app init,
  • then use it by name everywhere — no connection handle to thread around.

-

  • -- In your app's init or main:
  • let _ = Depot.DbRepo.start_from_env("MyApp.Repo", "DATABASE_URL",
  • "sqlite::memory:")
  • let _ = Depot.DbRepo.migrate("MyApp.Repo", MyApp.Migrations.all())

-

  • -- Anywhere in your app:
  • match Depot.DbRepo.query("MyApp.Repo", my_query) do
  • Ok(rows) -> rows
  • Err(e) -> ...
  • end

-

  • match Depot.DbRepo.transaction("MyApp.Repo", fn conn ->
  • Db.query(conn, "INSERT INTO ...")
  • ) do
  • Ok(_) -> ...
  • Err(e) -> ...
  • end

-

  • -- In tests (SQLite in-memory, no server required):
  • let _ = Depot.DbRepo.start("test_repo", "sqlite::memory:")
  • let _ = Depot.DbRepo.migrate("test_repo", MyApp.Migrations.all())

-

  • ## Design principles

-

  • - No magic: every operation is an explicit function call on a plain value.
  • - Non-short-circuiting validation: Gate collects all errors before you
  • inspect them, so users see a complete error list on first submit.
  • - Test-friendly: Depot.Repo is an in-memory store backed by Vault;
  • no database required for unit tests.
  • - Composable queries: Depot.Query builds a pure value describing the
  • query. Execution (in-memory vs SQL) is a separate step.