Elixir Client

Official SDK v0.1.0 Elixir 1.14+ / OTP 25+

Getting Started

Installation

Add to your mix.exs dependencies:

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

Then run:

mix deps.get

Requirements: Elixir 1.14+ and OTP 25+. The client uses msgpax for MessagePack serialization.

Quick Start

# Create and connect
{:ok, client} = SoliDB.Client.connect("127.0.0.1", 6745)

# Authenticate
:ok = SoliDB.Client.auth(client, "_system", "admin", "password")

# Set database context (required for management modules)
client = SoliDB.Client.use_database(client, "mydb")

# Basic CRUD operations
{:ok, doc} = SoliDB.Client.insert(client, "mydb", "users", %{name: "Alice", age: 30})
IO.puts("Created: #{doc["_key"]}")

{:ok, user} = SoliDB.Client.get(client, "mydb", "users", doc["_key"])
IO.puts("Retrieved: #{user["name"]}")

:ok = SoliDB.Client.update(client, "mydb", "users", doc["_key"], %{age: 31})

# Query with SDBQL
{:ok, results} = SoliDB.Client.query(client, "mydb", "FOR u IN users FILTER u.age > @min RETURN u", %{min: 25})
IO.puts("Found #{length(results)} users")

# Use management modules (functional style)
{:ok, scripts} = SoliDB.Scripts.list(client)
{:ok, triggers} = SoliDB.Triggers.list(client)

# Clean up
SoliDB.Client.close(client)

Connection Management

# Connect to SoliDB server
{:ok, client} = SoliDB.Client.connect("127.0.0.1", 6745)

# Check connection latency (returns ms)
{:ok, latency} = SoliDB.Client.ping(client)
IO.puts("Latency: #{Float.round(latency, 2)}ms")

# Close connection when done
SoliDB.Client.close(client)
Function Returns Description
connect(host, port){:ok, client}Establish TCP connection
ping(client){:ok, float}Latency in milliseconds
close(client):okClose connection
use_database(client, name)clientSet database context for modules

Authentication

# Authenticate with database, username, and password
:ok = SoliDB.Client.auth(client, "_system", "admin", "password")

# Authentication is required for most operations
# The session remains authenticated until disconnected

Core Operations

Database Operations

# List all databases
{:ok, databases} = SoliDB.Client.list_databases(client)
# => ["_system", "mydb", "testdb"]

# Create a new database
:ok = SoliDB.Client.create_database(client, "analytics")

# Delete a database
:ok = SoliDB.Client.delete_database(client, "old_db")
Function Returns Description
list_databases(client){:ok, list}List all database names
create_database(client, name):okCreate new database
delete_database(client, name):okDelete database

Collection Operations

# List collections in a database
{:ok, collections} = SoliDB.Client.list_collections(client, "mydb")
# => [%{"name" => "users", "type" => "document"}, ...]

# Create a document collection
:ok = SoliDB.Client.create_collection(client, "mydb", "products")

# Create an edge collection (for graphs)
:ok = SoliDB.Client.create_collection(client, "mydb", "relationships", "edge")

# Get collection statistics
{:ok, stats} = SoliDB.Client.collection_stats(client, "mydb", "users")
# => %{"count" => 1523, "size" => 245760, ...}

# Delete a collection
:ok = SoliDB.Client.delete_collection(client, "mydb", "old_collection")
Function Returns Description
list_collections(client, db){:ok, list}List collections in database
create_collection(client, db, name, type \\\\ nil):okCreate collection (type: document/edge)
collection_stats(client, db, name){:ok, map}Get collection statistics
delete_collection(client, db, name):okDelete collection

Document Operations (CRUD)

# INSERT - Create a new document
{:ok, doc} = SoliDB.Client.insert(client, "mydb", "users", %{
  name: "Alice",
  email: "[email protected]",
  age: 30
})
IO.puts(doc["_key"])  # Auto-generated key

# INSERT with custom key
{:ok, doc} = SoliDB.Client.insert(client, "mydb", "users", %{name: "Bob"}, "custom-key-123")

# GET - Retrieve a document by key
{:ok, user} = SoliDB.Client.get(client, "mydb", "users", "custom-key-123")
# => %{"_key" => "custom-key-123", "name" => "Bob", ...}

# UPDATE - Modify a document (merge by default)
:ok = SoliDB.Client.update(client, "mydb", "users", "custom-key-123", %{age: 25})

# UPDATE - Replace entire document (merge: false)
:ok = SoliDB.Client.update(client, "mydb", "users", "custom-key-123", %{name: "Robert"}, false)

# DELETE - Remove a document
:ok = SoliDB.Client.delete(client, "mydb", "users", "custom-key-123")

# LIST - Paginated document listing
{:ok, docs} = SoliDB.Client.list(client, "mydb", "users", 50, 0)  # limit: 50, offset: 0
Function Returns Description
insert(client, db, col, doc, key \\\\ nil){:ok, map}Insert document, returns doc with _key
get(client, db, col, key){:ok, map}Get document by key
update(client, db, col, key, doc, merge \\\\ true):okUpdate document (merge or replace)
delete(client, db, col, key):okDelete document
list(client, db, col, limit \\\\ 50, offset \\\\ 0){:ok, list}List documents with pagination

SDBQL Queries

# Simple query
{:ok, users} = SoliDB.Client.query(client, "mydb", "FOR u IN users RETURN u")

# Query with bind variables (recommended for security)
{:ok, results} = SoliDB.Client.query(client, "mydb", """
  FOR u IN users
  FILTER u.age >= @min_age AND u.status == @status
  SORT u.created_at DESC
  LIMIT @limit
  RETURN {name: u.name, email: u.email}
""", %{
  min_age: 18,
  status: "active",
  limit: 100
})

# Aggregation query
{:ok, stats} = SoliDB.Client.query(client, "mydb", """
  FOR u IN users
  COLLECT status = u.status WITH COUNT INTO count
  RETURN {status, count}
""")

# Join query
{:ok, orders} = SoliDB.Client.query(client, "mydb", """
  FOR o IN orders
  FOR u IN users FILTER u._key == o.user_id
  RETURN {order: o, user: u.name}
""")

# Explain query plan (for optimization)
{:ok, plan} = SoliDB.Client.explain(client, "mydb", "FOR u IN users FILTER u.age > 25 RETURN u")

ACID Transactions

# Begin a transaction
{:ok, tx_id} = SoliDB.Client.begin_transaction(client, "mydb", "read_committed")
# Isolation levels: read_uncommitted, read_committed, repeatable_read, serializable

try do
  # Perform operations within transaction
  {:ok, _} = SoliDB.Client.insert(client, "mydb", "accounts", %{id: 1, balance: 1000})
  {:ok, _} = SoliDB.Client.insert(client, "mydb", "accounts", %{id: 2, balance: 500})

  # Commit if all operations succeed
  :ok = SoliDB.Client.commit_transaction(client, tx_id)
  IO.puts("Transaction committed")
rescue
  error ->
    # Rollback on any error
    SoliDB.Client.rollback_transaction(client, tx_id)
    IO.puts("Transaction rolled back: #{inspect(error)}")
end
Function Returns Description
begin_transaction(client, db, isolation \\\\ nil){:ok, string}Start transaction, returns tx_id
commit_transaction(client, tx_id):okCommit transaction
rollback_transaction(client, tx_id):okRollback transaction

Index Management

# Create an index
:ok = SoliDB.Client.create_index(client, "mydb", "users", "idx_email", ["email"], true, false)
#                                 client   db      col       name        fields   unique sparse

# List indexes on a collection
{:ok, indexes} = SoliDB.Client.list_indexes(client, "mydb", "users")

# Delete an index
:ok = SoliDB.Client.delete_index(client, "mydb", "users", "idx_email")

Management Modules

Elixir uses a functional approach with separate modules instead of sub-clients. Important: Call use_database(client, name) first to set the database context.

SoliDB.Scripts

Lua Script Endpoints
client = SoliDB.Client.use_database(client, "mydb")

# Create a Lua script endpoint
{:ok, script} = SoliDB.Scripts.create(client,
  name: "hello",
  path: "/api/hello",
  methods: ["GET", "POST"],
  code: ~s(return {message = "Hello, " .. (req.params.name or "World")}),
  description: "Greeting endpoint",   # optional
  collection: "users"                 # optional: restrict to collection
)
IO.puts("Created script: #{script["_key"]}")

# List all scripts
{:ok, scripts} = SoliDB.Scripts.list(client)
Enum.each(scripts, fn s -> IO.puts("#{s["name"]} -> #{s["path"]}") end)

# Get a specific script
{:ok, script} = SoliDB.Scripts.get(client, "script_key")

# Update script code
:ok = SoliDB.Scripts.update(client, "script_key", %{
  code: ~s(return {message = "Updated!"}),
  methods: ["GET"]
})

# Delete a script
:ok = SoliDB.Scripts.delete(client, "script_key")

# Get execution statistics
{:ok, stats} = SoliDB.Scripts.get_stats(client)
IO.puts("Total calls: #{stats["total_calls"]}")
Function Parameters Description
create(client, opts)name:, path:, methods:, code:, description:, collection:Create Lua endpoint
list(client)-List all scripts
get(client, script_id)script_idGet script details
update(client, script_id, updates)script_id, mapUpdate script properties
delete(client, script_id)script_idDelete script
get_stats(client)-Execution statistics

SoliDB.Jobs & SoliDB.Cron

Background Processing
client = SoliDB.Client.use_database(client, "mydb")

# === JOBS ===

# List all queues
{:ok, queues} = SoliDB.Jobs.list_queues(client)
# => [%{"name" => "default", "pending" => 5, "running" => 2}, ...]

# List jobs in a queue with filters
{:ok, jobs} = SoliDB.Jobs.list_jobs(client, "default",
  status: "pending",  # pending, running, completed, failed
  limit: 50,
  offset: 0
)

# Enqueue a new job
{:ok, job} = SoliDB.Jobs.enqueue(client, "default",
  script_path: "/scripts/process-order",
  params: %{order_id: 12345},
  priority: 10,       # optional: higher = more urgent
  run_at: nil         # optional: ISO8601 datetime for delayed execution
)
IO.puts("Job ID: #{job["_key"]}")

# Get job details
{:ok, job} = SoliDB.Jobs.get(client, "job_id")
IO.puts("Status: #{job["status"]}")

# Cancel a pending job
:ok = SoliDB.Jobs.cancel(client, "job_id")

# === CRON ===

# List scheduled jobs
{:ok, crons} = SoliDB.Cron.list(client)

# Create a cron job
{:ok, cron} = SoliDB.Cron.create(client,
  name: "daily-cleanup",
  schedule: "0 2 * * *",              # Every day at 2 AM
  script_path: "/scripts/cleanup",
  params: %{days_old: 30},            # optional
  enabled: true,                      # optional
  description: "Remove old records"   # optional
)

# Get cron job details
{:ok, cron} = SoliDB.Cron.get(client, "cron_id")

# Update cron schedule
:ok = SoliDB.Cron.update(client, "cron_id", %{schedule: "0 3 * * *"})

# Toggle cron job on/off
:ok = SoliDB.Cron.toggle(client, "cron_id", false)  # disable
:ok = SoliDB.Cron.toggle(client, "cron_id", true)   # enable

# Delete cron job
:ok = SoliDB.Cron.delete(client, "cron_id")

SoliDB.Triggers

Database Triggers
client = SoliDB.Client.use_database(client, "mydb")

# List all triggers
{:ok, triggers} = SoliDB.Triggers.list(client)

# List triggers for a specific collection
{:ok, triggers} = SoliDB.Triggers.list_by_collection(client, "users")

# Create a trigger
{:ok, trigger} = SoliDB.Triggers.create(client,
  name: "on_user_created",
  collection: "users",
  event: "insert",                    # insert, update, delete
  timing: "after",                    # before, after
  script_path: "/scripts/on-user-create",
  enabled: true                       # optional
)

# Get trigger details
{:ok, trigger} = SoliDB.Triggers.get(client, "trigger_id")

# Update trigger
:ok = SoliDB.Triggers.update(client, "trigger_id", %{
  script_path: "/scripts/new-handler",
  enabled: false
})

# Toggle trigger on/off
:ok = SoliDB.Triggers.toggle(client, "trigger_id", true)   # enable
:ok = SoliDB.Triggers.toggle(client, "trigger_id", false)  # disable

# Delete trigger
:ok = SoliDB.Triggers.delete(client, "trigger_id")
Event Timing Description
insertbefore / afterFires on document creation
updatebefore / afterFires on document modification
deletebefore / afterFires on document removal

SoliDB.Roles & SoliDB.Users

Role-Based Access Control
# === ROLES ===

# List all roles
{:ok, roles} = SoliDB.Roles.list(client)

# Create a role with permissions
{:ok, role} = SoliDB.Roles.create(client,
  name: "editor",
  permissions: [
    %{action: "read", scope: "database", database: "mydb"},
    %{action: "write", scope: "collection", database: "mydb", collection: "articles"},
    %{action: "execute", scope: "script", database: "mydb"}
  ],
  description: "Content editor role"
)

# Get role details
{:ok, role} = SoliDB.Roles.get(client, "editor")

# Update role permissions
:ok = SoliDB.Roles.update(client, "editor", %{
  permissions: [
    %{action: "read", scope: "database", database: "mydb"},
    %{action: "write", scope: "database", database: "mydb"}
  ]
})

# Delete role
:ok = SoliDB.Roles.delete(client, "editor")

# === USERS ===

# List all users
{:ok, users} = SoliDB.Users.list(client)

# Create a user
{:ok, user} = SoliDB.Users.create(client,
  username: "john",
  password: "secure_password",
  roles: ["editor", "viewer"]  # optional
)

# Get user details
{:ok, user} = SoliDB.Users.get(client, "john")

# Get user's assigned roles
{:ok, roles} = SoliDB.Users.get_roles(client, "john")

# Assign a role to user
:ok = SoliDB.Users.assign_role(client, "john", "admin", database: "mydb")

# Revoke a role from user
:ok = SoliDB.Users.revoke_role(client, "john", "admin", database: "mydb")

# Get current authenticated user
{:ok, me} = SoliDB.Users.me(client)

# Get current user's permissions
{:ok, permissions} = SoliDB.Users.my_permissions(client)

# Change password
:ok = SoliDB.Users.change_password(client, "john", "old_password", "new_password")

# Delete user
:ok = SoliDB.Users.delete(client, "john")
Action Scopes Description
readdatabase, collectionRead documents and query
writedatabase, collectionCreate, update, delete documents
admindatabase, collectionManage indexes, schema, etc.
executescriptExecute Lua scripts

Advanced Features

SoliDB.Vector

Vector Search & AI
client = SoliDB.Client.use_database(client, "mydb")

# Create a vector index
{:ok, index} = SoliDB.Vector.create_index(client, "products",
  name: "product_embeddings",
  field: "embedding",
  dimensions: 1536,
  metric: "cosine"  # cosine, euclidean, dot_product
)

# Search by vector (semantic search)
embedding = get_embedding("wireless headphones")  # Your embedding function
{:ok, results} = SoliDB.Vector.search(client, "products",
  vector: embedding,
  limit: 10,
  filter: ~s(doc.category == "electronics")  # optional SDBQL filter
)

Enum.each(results, fn result ->
  IO.puts("#{result["doc"]["name"]} - Score: #{result["score"]}")
end)

# Search by existing document (find similar)
{:ok, similar} = SoliDB.Vector.search_by_document(client, "products",
  doc_key: "product-123",
  field: "embedding",
  limit: 5
)

# Quantize index (reduce memory usage)
:ok = SoliDB.Vector.quantize(client, "products", "product_embeddings", "binary")

# Dequantize (restore full precision)
:ok = SoliDB.Vector.dequantize(client, "products", "product_embeddings")

# Get index info
{:ok, info} = SoliDB.Vector.get_index_info(client, "products", "product_embeddings")

# List vector indexes
{:ok, indexes} = SoliDB.Vector.list_indexes(client, "products")

# Delete index
:ok = SoliDB.Vector.delete_index(client, "products", "product_embeddings")

SoliDB.Geo

Geospatial Queries
client = SoliDB.Client.use_database(client, "mydb")

# Create a geo index
:ok = SoliDB.Geo.create_index(client, "stores",
  name: "location_idx",
  fields: ["location"],     # Field containing [lat, lon] or GeoJSON
  geo_json: true            # optional: true if using GeoJSON format
)

# Find nearby locations (radius search)
{:ok, nearby} = SoliDB.Geo.near(client, "stores",
  latitude: 48.8566,
  longitude: 2.3522,
  radius: 5000,      # meters
  limit: 20          # optional
)

Enum.each(nearby, fn result ->
  IO.puts("#{result["doc"]["name"]} - #{result["distance"]}m away")
end)

# Find within polygon
polygon = %{
  type: "Polygon",
  coordinates: [[[2.3, 48.8], [2.4, 48.8], [2.4, 48.9], [2.3, 48.9], [2.3, 48.8]]]
}
{:ok, within} = SoliDB.Geo.within(client, "stores", geometry: polygon)

# Find intersecting geometries
{:ok, intersects} = SoliDB.Geo.intersects(client, "zones", geometry: polygon)

# Calculate distance between two points
{:ok, distance} = SoliDB.Geo.distance(client,
  lat1: 48.8566, lon1: 2.3522,
  lat2: 51.5074, lon2: -0.1278
)
IO.puts("Paris to London: #{distance / 1000}km")

# List geo indexes
{:ok, indexes} = SoliDB.Geo.list_indexes(client, "stores")

# Delete index
:ok = SoliDB.Geo.delete_index(client, "stores", "location_idx")

SoliDB.TTL

Time-To-Live Indexes
client = SoliDB.Client.use_database(client, "mydb")

# Create TTL index (auto-expire documents)
:ok = SoliDB.TTL.create_index(client, "sessions",
  name: "session_ttl",
  field: "created_at",            # DateTime field to check
  expire_after_seconds: 3600      # Expire after 1 hour
)

# Update expiration time
:ok = SoliDB.TTL.update_expiration(client, "sessions", "session_ttl", 7200)  # 2 hours

# Get index info
{:ok, info} = SoliDB.TTL.get_index_info(client, "sessions", "session_ttl")
IO.puts("Expires after: #{info["expire_after_seconds"]}s")

# Manually trigger cleanup (normally runs automatically)
{:ok, result} = SoliDB.TTL.run_cleanup(client, "sessions")
IO.puts("Deleted #{result["deleted"]} expired documents")

# List TTL indexes
{:ok, indexes} = SoliDB.TTL.list_indexes(client, "sessions")

# Delete TTL index
:ok = SoliDB.TTL.delete_index(client, "sessions", "session_ttl")

SoliDB.Columnar

Columnar/Analytics Storage
client = SoliDB.Client.use_database(client, "mydb")

# Create a columnar table (optimized for analytics)
{:ok, table} = SoliDB.Columnar.create(client, "metrics", [
  %{name: "timestamp", type: "datetime"},
  %{name: "metric_name", type: "string"},
  %{name: "value", type: "float"},
  %{name: "tags", type: "string"}
])

# Insert rows (batch insert is efficient)
:ok = SoliDB.Columnar.insert(client, "metrics", [
  %{timestamp: "2024-01-15T10:00:00Z", metric_name: "cpu_usage", value: 45.2, tags: "server1"},
  %{timestamp: "2024-01-15T10:01:00Z", metric_name: "cpu_usage", value: 47.8, tags: "server1"},
  %{timestamp: "2024-01-15T10:00:00Z", metric_name: "memory", value: 72.1, tags: "server1"}
])

# Query with SQL-like syntax
{:ok, results} = SoliDB.Columnar.query(client, "metrics",
  "SELECT * FROM metrics WHERE value > @min ORDER BY timestamp DESC LIMIT 100",
  params: %{min: 40.0}
)

# Aggregation
{:ok, agg} = SoliDB.Columnar.aggregate(client, "metrics", %{
  group_by: ["metric_name", "tags"],
  metrics: [
    %{column: "value", function: "avg"},
    %{column: "value", function: "max"},
    %{column: "value", function: "min"},
    %{column: "value", function: "count"}
  ],
  filters: %{metric_name: "cpu_usage"}  # optional
})

# Get table statistics
{:ok, stats} = SoliDB.Columnar.stats(client, "metrics")
IO.puts("Row count: #{stats["row_count"]}, Size: #{stats["size_bytes"]}")

# Add a column
:ok = SoliDB.Columnar.add_column(client, "metrics",
  column_name: "host",
  column_type: "string",
  default_value: "unknown"  # optional
)

# Drop a column
:ok = SoliDB.Columnar.drop_column(client, "metrics", "host")

# Create index on columnar table
:ok = SoliDB.Columnar.create_index(client, "metrics",
  index_name: "idx_timestamp",
  column: "timestamp",
  index_type: "btree"  # optional
)

# List indexes
{:ok, indexes} = SoliDB.Columnar.list_indexes(client, "metrics")

# Delete index
:ok = SoliDB.Columnar.delete_index(client, "metrics", "idx_timestamp")

# List all columnar tables
{:ok, tables} = SoliDB.Columnar.list(client)

# Get table info
{:ok, table} = SoliDB.Columnar.get(client, "metrics")

# Delete table
:ok = SoliDB.Columnar.delete(client, "metrics")

SoliDB.Cluster

Cluster Management
# Get cluster status
{:ok, status} = SoliDB.Cluster.status(client)
IO.puts("Mode: #{status["mode"]}")  # standalone, cluster
IO.puts("Nodes: #{status["node_count"]}")

# Get detailed cluster info
{:ok, info} = SoliDB.Cluster.info(client)

# Get all nodes
{:ok, nodes} = SoliDB.Cluster.get_nodes(client)
Enum.each(nodes, fn node ->
  IO.puts("#{node["id"]}: #{node["address"]} (#{node["status"]})")
end)

# Get shard distribution
{:ok, shards} = SoliDB.Cluster.get_shards(client)

# Remove a node from cluster
:ok = SoliDB.Cluster.remove_node(client, "node-id-to-remove")

# Trigger data rebalancing
:ok = SoliDB.Cluster.rebalance(client)

# Cleanup orphaned data
:ok = SoliDB.Cluster.cleanup(client)

# Reshard cluster
:ok = SoliDB.Cluster.reshard(client, 16)  # new number of shards

SoliDB.CollectionsOps

Advanced Collection Operations
client = SoliDB.Client.use_database(client, "mydb")

# Truncate collection (delete all documents)
:ok = SoliDB.CollectionsOps.truncate(client, "logs")

# Compact collection (reclaim disk space)
:ok = SoliDB.CollectionsOps.compact(client, "users")

# Repair collection (fix inconsistencies)
:ok = SoliDB.CollectionsOps.repair(client, "orders")

# Get collection statistics
{:ok, stats} = SoliDB.CollectionsOps.stats(client, "users")

# Prune old documents
:ok = SoliDB.CollectionsOps.prune(client, "logs",
  older_than: "2024-01-01T00:00:00Z",
  field: "created_at"
)

# Recount documents
:ok = SoliDB.CollectionsOps.recount(client, "users")

# Set JSON schema validation
:ok = SoliDB.CollectionsOps.set_schema(client, "users", %{
  type: "object",
  required: ["name", "email"],
  properties: %{
    name: %{type: "string", minLength: 1},
    email: %{type: "string", format: "email"},
    age: %{type: "integer", minimum: 0}
  }
})

# Get current schema
{:ok, schema} = SoliDB.CollectionsOps.get_schema(client, "users")

# Remove schema validation
:ok = SoliDB.CollectionsOps.delete_schema(client, "users")

# Export collection
{:ok, data} = SoliDB.CollectionsOps.export(client, "users", "json")  # json, csv, msgpack

# Import data
:ok = SoliDB.CollectionsOps.import(client, "users_backup", data, "json")

# Get sharding configuration
{:ok, sharding} = SoliDB.CollectionsOps.get_sharding(client, "orders")

# Configure sharding
:ok = SoliDB.CollectionsOps.set_sharding(client, "orders", %{
  num_shards: 8,
  shard_key: "user_id"
})

SoliDB.Env

Environment Variables
client = SoliDB.Client.use_database(client, "mydb")

# List environment variables (for Lua scripts)
{:ok, vars} = SoliDB.Env.list(client)

# Set an environment variable
:ok = SoliDB.Env.set(client, "API_KEY", "sk-xxx-your-api-key")
:ok = SoliDB.Env.set(client, "WEBHOOK_URL", "https://example.com/webhook")

# Delete an environment variable
:ok = SoliDB.Env.delete(client, "OLD_VAR")

Error Handling

# All functions return {:ok, result} or {:error, reason}
case SoliDB.Client.connect("127.0.0.1", 6745) do
  {:ok, client} ->
    case SoliDB.Client.auth(client, "mydb", "user", "password") do
      :ok ->
        case SoliDB.Client.get(client, "mydb", "users", "nonexistent-key") do
          {:ok, doc} ->
            IO.inspect(doc)

          {:error, %SoliDB.Error{type: :not_found}} ->
            IO.puts("Document not found")

          {:error, %SoliDB.Error{type: :permission_denied, message: msg}} ->
            IO.puts("Access denied: #{msg}")

          {:error, error} ->
            IO.puts("Server error: #{inspect(error)}")
        end

      {:error, reason} ->
        IO.puts("Authentication failed: #{inspect(reason)}")
    end
    SoliDB.Client.close(client)

  {:error, %SoliDB.Error{type: :connection_refused}} ->
    IO.puts("Cannot connect to server")

  {:error, %SoliDB.Error{type: :timeout}} ->
    IO.puts("Connection timeout")
end

Connection Errors

:connection_refused, :timeout, :closed - Network failures and disconnections

Server Errors

:not_found, :permission_denied, :validation_error - Server-side issues

Protocol Errors

:decode_error, :invalid_response - Serialization issues