Mix.install([
{:terminusdb_client, "~> 0.3.3"}
])Setup
Start a TerminusDB server first:
docker run -d --name terminusdb -p 6363:6363 -e TERMINUSDB_ADMIN_PASS=root terminusdb/terminusdb-server:latest
Then wait for it to be ready:
endpoint = "http://localhost:6363"
# Wait for the server to be ready
for _ <- 1..30 do
case Req.get("#{endpoint}/api/ok") do
{:ok, %{status: 200}} -> :ok
_ -> Process.sleep(1000)
end
end1. Configuration
alias TerminusDB.{Config, Database, Document, Schema, Branch, Client}
config = Config.new(endpoint: endpoint)# Inspect auth
Config.auth(config)# Redact secrets for safe logging
Config.redact(config)2. Database management
# Create a database with a schema graph
db_name = "livebook_demo-tester"
if Database.exists?(config, db_name) do
{:ok, _} = Database.delete(config, db_name, force: true)
end
{:ok, _} =
Database.create(config, db_name,
label: "Livebook Demo",
comment: "A database for the livebook walkthrough",
schema: true
)# Check it exists
Database.exists?(config, "livebook_demo")# List all databases
{:ok, dbs} = Database.list(config)
3. Document operations
Scope the config to the database:
config = Config.with_database(config, db_name)Insert a schema
{:ok, _} =
Document.insert(config,
%{
"@type" => "Class",
"@id" => "Person",
"name" => "xsd:string",
"age" => "xsd:integer",
"email" => "xsd:string"
},
author: "admin",
message: "Add Person schema",
graph_type: :schema
)Insert documents
{:ok, [alice_id|_]} =
Document.insert(config,
%{"@type" => "Person", "name" => "Alice", "age" => 30, "email" => "alice@example.com"},
author: "admin",
message: "Add Alice"
){:ok, [bobs_id|_]} =
Document.insert(config, [
%{"@type" => "Person", "name" => "Bob", "age" => 25, "email" => "bob@example.com"},
%{"@type" => "Person", "name" => "Carol", "age" => 28, "email" => "carol@example.com"}
], author: "admin", message: "Add Bob and Carol")Retrieve documents
{:ok, docs} = Document.get(config, type: "Person", as_list: true)
Enum.map(docs, & &1["name"]){:ok, matches} =
Document.query(config, %{"@type" => "Person", "age" => 28})
Enum.map(matches, & &1["name"]){:ok, person} = Document.get(config, id: alice_id, as_list: false)
{:ok, _} =
Document.replace(config,
Map.put(person, "age", 31),
author: "admin",
message: "Happy birthday Alice"
)
{:ok, _updated_person} = Document.get(config, id: alice_id, as_list: false)Delete a document
{:ok, _} = Document.delete(config,
id: bobs_id,
author: "admin",
message: "Remove Bob"
)4. Schema frames
{:ok, _frame} = Schema.frame(config, "Person") => %{"@type" => "Class", "name" => "xsd:string", "age" => "xsd:integer", ...}{:ok, all} = Schema.all(config)
Map.keys(all)5. Branches
# Create a branch
{:ok, _} = Branch.create(config, "feature-x")# It exists
Branch.exists?(config, "feature-x")# Switch to it
feature_config = Config.with_branch(config, "feature-x")
# Insert on the branch
{:ok, _} =
Document.insert(feature_config,
%{"@type" => "Person", "name" => "Dave", "age" => 40, "email" => "dave@example.com"},
author: "admin",
message: "Add Dave on feature branch"
)# Dave is on the feature branch
{:ok, feature_docs} = Document.get(feature_config, type: "Person", as_list: true)
"Dave" in Enum.map(feature_docs, & &1["name"])# Dave is NOT on the main branch
{:ok, main_docs} = Document.get(config, type: "Person", as_list: true)
"Dave" in Enum.map(main_docs, & &1["name"]) => false# Delete the branch
{:ok, _} = Branch.delete(config, "feature-x")6. Streaming
# Stream documents one at a time (constant memory for large result sets)
Document.stream(config, type: "Person")
|> Stream.map(& &1["name"])
|> Enum.to_list()7. Telemetry
:telemetry.attach_many(
"livebook-telemetry",
[[:terminusdb, :document, :stop], [:terminusdb, :database, :stop]],
fn _event, measurements, meta, _ctx ->
duration_ms = System.convert_time_unit(measurements[:duration] || 0, :native, :millisecond)
IO.puts("[#{meta.area}] #{meta.method} #{meta.path} -> #{meta.status} (#{duration_ms}ms)")
end,
nil
)# Now operations emit telemetry events
{:ok, _} = Document.insert(config,
%{"@type" => "Person", "name" => "Eve", "age" => 35, "email" => "eve@example.com"},
author: "admin",
message: "Add Eve"
) [document] :post document/admin/livebook_demo -> 200 (15ms):telemetry.detach("livebook-telemetry")8. Error handling
# Tuple-returning (non-raising)
case Database.create(config, "livebook_demo", label: "Duplicate") do
{:ok, _} ->
IO.puts("Created (unexpected)")
{:error, %{reason: :api, api_type: "api:DatabaseAlreadyExists"} = e} ->
IO.puts("Expected error: #{Exception.message(e)}")
{:error, %{reason: :api} = e} ->
IO.puts("Other API error: #{Exception.message(e)}")
{:error, e} ->
IO.puts("Other error: #{inspect(e)}")
end Expected error: TerminusDB API error 400 (api:DatabaseAlreadyExists): Database already exists.# Raising variant
try do
Database.create!(config, "livebook_demo", label: "Duplicate")
rescue
e in TerminusDB.Error ->
IO.puts("Raised as expected: #{Exception.message(e)}")
end ** (TerminusDB.Error) TerminusDB API error 400 (api:DatabaseAlreadyExists): Database already exists.9. Raw client
# Direct access to any endpoint
{:ok, info} = Client.request(config, :get, "info")
info["api:info"]["terminusdb"]["version"] => "12.0.5"Cleanup
{:ok, _} = Database.delete(config, "livebook_demo", force: true)# Stop the container
# System.cmd("docker", ["stop", "terminusdb"])
# System.cmd("docker", ["rm", "terminusdb"])