Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Exadb

Exadb is a pragmatic Elixir client for ArangoDB's HTTP API.

It is built for the way teams actually work with ArangoDB in real applications:

  • fetch a document
  • edit it as a plain Elixir map
  • persist it back
  • run AQL when the job is bigger than one document
  • stream cursor pages when result sets grow large

That workflow has been used in production for years. The code was stable and useful long before it was packaged for public release.

Exadb stays intentionally small and direct:

  • plain maps in and out
  • minimal ceremony
  • focused modules for documents, queries, collections, graphs, users, and databases
  • easy to understand request flow when something needs debugging

If you want a thin, dependable layer over ArangoDB instead of a large abstraction that hides the database, Exadb is the right shape.

Installation

Add :exadb to your dependencies:

defp deps do
  [
    {:exadb, "~> 0.1.0"}
  ]
end

Then fetch dependencies:

mix deps.get

Configuration

The client reads these environment variables by default:

  • ADB_URL defaults to arangodb:8529
  • ADB_USER defaults to root
  • ADB_PWD defaults to the same value as ADB_USER
  • ADB_DB defaults to the same value as ADB_USER, then default

You can also pass explicit options to every function:

opts = [url: "localhost:8529", user: "root", pwd: "secret", db: "example"]

This makes it easy to use Exadb in both styles:

  • environment-driven application code
  • explicit per-call scripts, tasks, tests, or admin tooling

Why Exadb

  • The core document workflow is simple and fast.
  • persist/2 is smart: insert new maps, update fetched documents.
  • You keep working with ordinary maps instead of custom structs.
  • Query helpers are thin and predictable.
  • Database and user helpers make multi-tenant or admin flows straightforward.

Feature List

Exadb currently covers these ArangoDB workflows:

  • Document CRUD with plain maps via Exadb.Doc.fetch/2, persist/2, persist_multi/2, persist_new/2, vaporize/2, and vaporize_by_id/2
  • Multi-document and lookup helpers via Exadb.Doc.fetch_multi/2, get/3, get_one/2, and is_there?/2
  • Targeted document property updates via Exadb.Doc.update_property/4, push/4, pop/4, switch_on/3, and switch_off/3
  • AQL execution with bind variables via Exadb.Query.run/3
  • Cursor pagination and streaming via Exadb.Query.cursor/2 and cursor_stream/2
  • Collection management via Exadb.Collection.new_collection/3, new_edge/3, new/4, rename/3, get_all/1, get/2, is_there?/2, and delete/2
  • Index management via Exadb.Index.new/3, list/2, clean_list/2, and delete/2
  • Named graph management via Exadb.Graph.new/4, get_all/1, and delete/2
  • Database administration via Exadb.Database.get_all/1, user_dbs/2, new/2, new_db_and_user/3, and vaporize/2
  • User administration and database grants via Exadb.User.new/2, get_all/1, get/2, replace/3, update/3, give_access/3, remove_access/3, and vaporize/2
  • Database and collection copy helpers via Exadb.Manager.copy_database/3, copy_collection_data/2, copy_collection_data_filtered/2, copy_query/2, and clean_database/1
  • URL and database-link helpers via Exadb.Api.url/1, root/1, and db/2

The Main Workflow

The most natural Exadb workflow is:

  1. fetch a document
  2. edit the map
  3. persist it back

Exadb.Doc.persist/2 decides what to do from the content you give it.

  • If the map is new, it creates a document.
  • If the map already contains ArangoDB document metadata from a previous fetch, it updates the existing document.

That means the common edit cycle stays clean:

opts = [url: "localhost:8529", user: "root", pwd: "secret", db: "app", col: "users"]

{:ok, user} = Exadb.Doc.fetch("users/123", opts)

{:ok, updated_user} =
  user
  |> Map.put("display_name", "Jane Doe")
  |> Map.put("active", true)
  |> Exadb.Doc.persist(opts)

No separate create-vs-update branch in your application code.

Query And Cursor Workflow

Not every job is a single-document workflow.

When the work is better expressed in AQL, Exadb gives you a second natural path:

  1. write the AQL you actually want
  2. pass bind variables as a plain map
  3. read the decoded result
  4. move to cursor paging or streaming when the result set grows

That means you can use Exadb comfortably in both modes:

  • document-centric application code with Exadb.Doc
  • query-centric reporting, search, migration, and batch processing with Exadb.Query

For straightforward AQL execution:

opts = [url: "localhost:8529", user: "root", pwd: "secret", db: "app"]

{:ok, users} = Exadb.Query.run(
  "FOR user IN users FILTER user.active == @active SORT user.email RETURN user",
  %{active: true},
  opts
)

For cursor-based reads, start a cursor with a map:

{:ok, first_page} =
  Exadb.Query.cursor(
    %{
      query: "FOR user IN users SORT user.email RETURN user",
      batchSize: 100
    },
    opts
  )

If ArangoDB returns more results, pass the cursor response back into Exadb.Query.cursor/2 to fetch the next page. When the cursor is exhausted, cursor/2 returns {:done, last_page} instead of {:ok, page}.

If you want a cleaner streaming model, use Exadb.Query.cursor_stream/2:

Exadb.Query.cursor_stream(
  %{
    query: "FOR user IN users SORT user.email RETURN user",
    batchSize: 100
  },
  opts
)
|> Enum.each(fn page ->
  Enum.each(page["result"], fn user ->
    IO.inspect(user["email"])
  end)
end)

This is especially useful for:

  • reporting
  • export jobs
  • background processing
  • data migrations
  • copying data between databases

Create New Documents

For new data, just pass a plain map:

opts = [url: "localhost:8529", user: "root", pwd: "secret", db: "app", col: "users"]

{:ok, created} =
  Exadb.Doc.persist(%{
    "email" => "jane@example.com",
    "display_name" => "Jane",
    "active" => true,
    "roles" => ["admin"]
  }, opts)

created["_id"]
#=> "users/123"

Clone Or Reinsert Without Metadata

If you fetched a document and want to store it as a fresh record instead of updating the existing one, use persist_new/2.

{:ok, copy} =
  created
  |> Map.put("email", "copy@example.com")
  |> Exadb.Doc.persist_new(opts)

Usage

Documents

Exadb is at its best when your application logic is document-centric.

opts = [url: "localhost:8529", user: "root", pwd: "secret", db: "app", col: "users"]

{:ok, created} = Exadb.Doc.persist(%{"email" => "jane@example.com", "tags" => ["new"]}, opts)
{:ok, found} = Exadb.Doc.fetch(created["_id"], opts)

{:ok, retagged} =
  found
  |> Map.put("tags", ["new", "customer"])
  |> Exadb.Doc.persist(opts)

Exadb.Doc.push(retagged["_id"], "tags", "beta", opts)
Exadb.Doc.pop(retagged["_id"], "tags", "new", opts)
Exadb.Doc.switch_on(retagged["_id"], "active", opts)

Other useful document helpers:

  • Exadb.Doc.fetch_multi/2
  • Exadb.Doc.get/3
  • Exadb.Doc.get_one/2
  • Exadb.Doc.is_there?/2
  • Exadb.Doc.vaporize/2
  • Exadb.Doc.vaporize_by_id/2

Queries

For AQL-heavy parts of an application, use Exadb.Query.

opts = [url: "localhost:8529", user: "root", pwd: "secret", db: "app"]

{:ok, users} = Exadb.Query.run(
  "FOR user IN users FILTER user.email == @email RETURN user",
  %{email: "jane@example.com"},
  opts
)

If you need cursor-based processing, Exadb.Query.cursor/2 and Exadb.Query.cursor_stream/2 let you work through larger result sets without changing mental models.

Collections, Edges, And Indexes

Schema-level operations stay just as direct.

db_opts = [url: "localhost:8529", user: "root", pwd: "secret", db: "app"]

Exadb.Collection.new_collection("users", %{}, db_opts)
Exadb.Collection.new_edge("follows", %{}, db_opts)

Exadb.Index.new(
  "users",
  %{"type" => "persistent", "fields" => ["email"], "unique" => true},
  db_opts
)

This is enough to keep bootstrapping, migration scripts, and admin tooling readable.

Graphs

When you need named graph setup, Exadb keeps that API small too:

db_opts = [url: "localhost:8529", user: "root", pwd: "secret", db: "app"]

Exadb.Graph.new(
  "social",
  [
    %{
      collection: "follows",
      from: ["users"],
      to: ["users"]
    }
  ],
  [],
  db_opts
)

Users And Databases

Exadb also covers the practical admin operations needed by real systems.

admin_opts = [url: "localhost:8529", user: "root", pwd: "secret"]

Exadb.Database.new_db_and_user("tenant_a", "tenant-secret", admin_opts)
Exadb.User.give_access("tenant_a", "tenant_a", Keyword.merge(admin_opts, level: "rw"))

For operational data movement, Exadb.Manager also includes higher-level copy helpers. That covers cases such as copying a database schema into a new tenant database, cloning collection data, or moving records selected by a custom AQL filter.

That makes it useful not just in application code, but also in:

  • provisioning tasks
  • tenant setup flows
  • migration scripts
  • operational admin commands

URL Helpers

If you want to build URLs once and pass them through explicitly, the API helpers are available too:

Exadb.Api.url(url: "localhost:8529", user: "root", pwd: "secret")
#=> "http://root:secret@localhost:8529"

Exadb.Api.root(url: "localhost:8529", user: "root", pwd: "secret")
#=> "http://root:secret@localhost:8529/_api"

Exadb.Api.db("app", url: "localhost:8529", user: "root", pwd: "secret")
#=> "http://root:secret@localhost:8529/_db/app/_api"

Testing And Documentation

Run the normal test suite:

mix test

Run integration tests against a real ArangoDB instance:

mix test --include integration
EXADB_RUN_INTEGRATION=1 mix test

The integration suite supports these environment variables:

  • EXADB_TEST_DB, default mix_test
  • EXADB_TEST_URL, default localhost:8529
  • EXADB_TEST_USER, default root
  • EXADB_TEST_PWD, default root

By default, the integration suite recreates the mix_test database and matching user before the run and removes them afterwards.

Generate docs locally with:

mix docs

Additional guides:

  • guides/getting-started.md
  • guides/document-workflow.md
  • guides/queries-and-cursors.md
  • guides/schema-and-operations.md
  • guides/testing.md

Exadb is production-proven code that stayed private for a long time. Its design is direct because it grew out of repeated real-world use, not from trying to model every possible abstraction up front.

If you want a client that makes ArangoDB pleasant to use from Elixir without forcing you into a heavy framework, Exadb is ready for that job.

About

Exadb is a pragmatic Elixir client for ArangoDB's HTTP API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages