PHP Client

Official SDK v0.1.0 PHP 8.0+

Getting Started

Installation

Using Composer:

composer require solidb/php-client

In composer.json:

"require": {
    "solidb/php-client": "^0.1.0"
}

Requirements: PHP 8.0 or higher. The client requires either the msgpack PHP extension or the rybakit/msgpack library for binary serialization.

Quick Start

<?php
use SoliDB\Client;

// Create client instance
$client = new Client('127.0.0.1', 6745);

// Connect to server (auto-connects if not called)
$client->connect();

// Authenticate
$client->auth('_system', 'admin', 'password');

// Set database context (required for sub-clients)
$client->useDatabase('mydb');

// Basic CRUD operations
$doc = $client->insert('mydb', 'users', ['name' => 'Alice', 'age' => 30]);
echo "Created: " . $doc['_key'] . "\n";

$user = $client->get('mydb', 'users', $doc['_key']);
echo "Retrieved: " . $user['name'] . "\n";

$client->update('mydb', 'users', $doc['_key'], ['age' => 31]);

// Query with SDBQL
$results = $client->query('mydb', 'FOR u IN users FILTER u.age > @min RETURN u', ['min' => 25]);
echo "Found " . count($results) . " users\n";

// Use management sub-clients
$scripts = $client->scripts()->list();
$triggers = $client->triggers()->list();

// Clean up
$client->close();

Connection Management

// Initialize with host and port
$client = new Client('127.0.0.1', 6745);

// Connect (establishes TCP socket with binary protocol)
$client->connect();

// Check connection latency (returns ms)
$latency = $client->ping();
echo "Latency: " . round($latency, 2) . "ms\n";

// Close connection when done
$client->close();
Method Returns Description
new Client($host, $port)ClientCreate client instance
connect()voidEstablish TCP connection
ping()floatLatency in milliseconds
close()voidClose connection
useDatabase($name)selfSet database context for sub-clients
getDatabase()?stringGet current database context

Authentication

// Authenticate with database, username, and password
$client->auth('_system', 'admin', 'password');

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

// Authenticate to a specific database
$client->auth('mydb', 'myuser', 'userpassword');

Core Operations

Database Operations

// List all databases
$databases = $client->listDatabases();
// => ["_system", "mydb", "testdb"]

// Create a new database
$client->createDatabase('analytics');

// Delete a database
$client->deleteDatabase('old_db');
Method Returns Description
listDatabases()arrayList all database names
createDatabase($name)voidCreate new database
deleteDatabase($name)voidDelete database

Collection Operations

// List collections in a database
$collections = $client->listCollections('mydb');
// => [["name" => "users", "type" => "document"], ...]

// Create a document collection
$client->createCollection('mydb', 'products');

// Create an edge collection (for graphs)
$client->createCollection('mydb', 'relationships', 'edge');

// Get collection statistics
$stats = $client->collectionStats('mydb', 'users');
// => ["count" => 1523, "size" => 245760, ...]

// Delete a collection
$client->deleteCollection('mydb', 'old_collection');
Method Returns Description
listCollections($db)arrayList collections in database
createCollection($db, $name, $type = null)voidCreate collection (type: document/edge)
collectionStats($db, $name)arrayGet collection statistics
deleteCollection($db, $name)voidDelete collection

Document Operations (CRUD)

// INSERT - Create a new document
$doc = $client->insert('mydb', 'users', [
    'name' => 'Alice',
    'email' => '[email protected]',
    'age' => 30
]);
echo $doc['_key'];  // Auto-generated key

// INSERT with custom key
$doc = $client->insert('mydb', 'users', ['name' => 'Bob'], 'custom-key-123');

// GET - Retrieve a document by key
$user = $client->get('mydb', 'users', 'custom-key-123');
// => ["_key" => "custom-key-123", "name" => "Bob", ...]

// UPDATE - Modify a document (merge by default)
$client->update('mydb', 'users', 'custom-key-123', ['age' => 25]);

// UPDATE - Replace entire document (merge = false)
$client->update('mydb', 'users', 'custom-key-123', ['name' => 'Robert'], false);

// DELETE - Remove a document
$client->delete('mydb', 'users', 'custom-key-123');
Method Returns Description
insert($db, $col, $doc, $key = null)arrayInsert document, returns doc with _key
get($db, $col, $key)?arrayGet document by key
update($db, $col, $key, $doc, $merge = true)voidUpdate document (merge or replace)
delete($db, $col, $key)voidDelete document

SDBQL Queries

// Simple query
$users = $client->query('mydb', 'FOR u IN users RETURN u');

// Query with bind variables (recommended for security)
$results = $client->query('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
$stats = $client->query('mydb', '
    FOR u IN users
    COLLECT status = u.status WITH COUNT INTO count
    RETURN { status, count }
');

// Join query
$orders = $client->query('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)
$plan = $client->explain('mydb', 'FOR u IN users FILTER u.age > 25 RETURN u');

ACID Transactions

// Begin a transaction
$txId = $client->beginTransaction('mydb', 'read_committed');
// Isolation levels: read_uncommitted, read_committed, repeatable_read, serializable

try {
    // Perform operations within transaction
    $client->insert('mydb', 'accounts', ['id' => 1, 'balance' => 1000]);
    $client->insert('mydb', 'accounts', ['id' => 2, 'balance' => 500]);

    // Commit if all operations succeed
    $client->commitTransaction($txId);
    echo "Transaction committed\n";
} catch (Exception $e) {
    // Rollback on any error
    $client->rollbackTransaction($txId);
    echo "Transaction rolled back: " . $e->getMessage() . "\n";
}
Method Returns Description
beginTransaction($db, $isolation)stringStart transaction, returns tx_id
commitTransaction($txId)voidCommit transaction
rollbackTransaction($txId)voidRollback transaction

Index Management

// Create an index
$client->createIndex('mydb', 'users', 'idx_email', ['email'], true, false);
//                   $db    $col     $name       $fields   $unique $sparse

// List indexes on a collection
$indexes = $client->listIndexes('mydb', 'users');

// Delete an index
$client->deleteIndex('mydb', 'users', 'idx_email');

Management Sub-Clients

Sub-clients provide namespaced access to management APIs. Important: Call useDatabase($name) first to set the database context.

scripts()
jobs()
cron()
triggers()
env()
roles()
users()
apiKeys()
cluster()
collectionsOps()
indexesOps()
geo()
vector()
ttl()
columnar()

$client->scripts()

Lua Script Endpoints
$client->useDatabase('mydb');

// Create a Lua script endpoint
$script = $client->scripts()->create(
    'hello',                    // name
    '/api/hello',               // path
    ['GET', 'POST'],            // methods
    'return { message = "Hello, " .. (req.params.name or "World") }',  // code
    'Greeting endpoint',        // description (optional)
    null                        // collection (optional: restrict to collection)
);
echo "Created script: " . $script['_key'] . "\n";

// List all scripts
$scripts = $client->scripts()->list();
foreach ($scripts as $s) {
    echo $s['name'] . " -> " . $s['path'] . "\n";
}

// Get a specific script
$script = $client->scripts()->get('script_key');

// Update script code
$client->scripts()->update('script_key', [
    'code' => 'return { message = "Updated!" }',
    'methods' => ['GET']
]);

// Delete a script
$client->scripts()->delete('script_key');

// Get execution statistics
$stats = $client->scripts()->getStats();
echo "Total calls: " . $stats['total_calls'] . "\n";
Method Parameters Description
create()$name, $path, $methods, $code, $description, $collectionCreate Lua endpoint
list()-List all scripts
get($scriptId)$scriptIdGet script details
update($scriptId, $updates)$scriptId, arrayUpdate script properties
delete($scriptId)$scriptIdDelete script
getStats()-Execution statistics

$client->jobs() & $client->cron()

Background Processing
$client->useDatabase('mydb');

// === JOBS ===

// List all queues
$queues = $client->jobs()->listQueues();
// => [["name" => "default", "pending" => 5, "running" => 2], ...]

// List jobs in a queue with filters
$jobs = $client->jobs()->listJobs('default', 'pending', 50, 0);
//                               $queueName, $status, $limit, $offset

// Enqueue a new job
$job = $client->jobs()->enqueue(
    'default',                  // queue name
    '/scripts/process-order',   // script path
    ['order_id' => 12345],      // params (optional)
    10,                         // priority (optional: higher = more urgent)
    null                        // run_at (optional: ISO8601 for delayed execution)
);
echo "Job ID: " . $job['_key'] . "\n";

// Get job details
$job = $client->jobs()->get('job_id');
echo "Status: " . $job['status'] . "\n";

// Cancel a pending job
$client->jobs()->cancel('job_id');

// === CRON ===

// List scheduled jobs
$crons = $client->cron()->list();

// Create a cron job
$cron = $client->cron()->create(
    'daily-cleanup',            // name
    '0 2 * * *',                // schedule (cron expression)
    '/scripts/cleanup',         // script path
    ['days_old' => 30],         // params (optional)
    true,                       // enabled (optional)
    'Remove old records'        // description (optional)
);

// Get cron job details
$cron = $client->cron()->get('cron_id');

// Update cron schedule
$client->cron()->update('cron_id', ['schedule' => '0 3 * * *']);

// Toggle cron job on/off
$client->cron()->toggle('cron_id', false);  // disable
$client->cron()->toggle('cron_id', true);   // enable

// Delete cron job
$client->cron()->delete('cron_id');

$client->triggers()

Database Triggers
$client->useDatabase('mydb');

// List all triggers
$triggers = $client->triggers()->list();

// List triggers for a specific collection
$triggers = $client->triggers()->listByCollection('users');

// Create a trigger
$trigger = $client->triggers()->create(
    'on_user_created',              // name
    'users',                        // collection
    'insert',                       // event: insert, update, delete
    'after',                        // timing: before, after
    '/scripts/on-user-create',      // script path
    true                            // enabled (optional)
);

// Get trigger details
$trigger = $client->triggers()->get('trigger_id');

// Update trigger
$client->triggers()->update('trigger_id', [
    'script_path' => '/scripts/new-handler',
    'enabled' => false
]);

// Toggle trigger on/off
$client->triggers()->toggle('trigger_id', true);   // enable
$client->triggers()->toggle('trigger_id', false);  // disable

// Delete trigger
$client->triggers()->delete('trigger_id');
Event Timing Description
insertbefore / afterFires on document creation
updatebefore / afterFires on document modification
deletebefore / afterFires on document removal

$client->roles() & $client->users()

Role-Based Access Control
// === ROLES ===

// List all roles
$roles = $client->roles()->list();

// Create a role with permissions
$role = $client->roles()->create(
    'editor',                               // name
    [                                       // permissions
        ['action' => 'read', 'scope' => 'database', 'database' => 'mydb'],
        ['action' => 'write', 'scope' => 'collection', 'database' => 'mydb', 'collection' => 'articles'],
        ['action' => 'execute', 'scope' => 'script', 'database' => 'mydb']
    ],
    'Content editor role'                   // description (optional)
);

// Get role details
$role = $client->roles()->get('editor');

// Update role permissions
$client->roles()->update('editor', [
    ['action' => 'read', 'scope' => 'database', 'database' => 'mydb'],
    ['action' => 'write', 'scope' => 'database', 'database' => 'mydb']
]);

// Delete role
$client->roles()->delete('editor');

// === USERS ===

// List all users
$users = $client->users()->list();

// Create a user
$user = $client->users()->create(
    'john',                     // username
    'secure_password',          // password
    ['editor', 'viewer']        // roles (optional)
);

// Get user details
$user = $client->users()->get('john');

// Get user's assigned roles
$roles = $client->users()->getRoles('john');

// Assign a role to user
$client->users()->assignRole('john', 'admin', 'mydb');

// Revoke a role from user
$client->users()->revokeRole('john', 'admin', 'mydb');

// Get current authenticated user
$me = $client->users()->me();

// Get current user's permissions
$permissions = $client->users()->myPermissions();

// Change password
$client->users()->changePassword('john', 'old_password', 'new_password');

// Delete user
$client->users()->delete('john');
Action Scopes Description
readdatabase, collectionRead documents and query
writedatabase, collectionCreate, update, delete documents
admindatabase, collectionManage indexes, schema, etc.
executescriptExecute Lua scripts

$client->apiKeys()

API Key Management
// List all API keys
$apiKeys = $client->apiKeys()->list();

// Create an API key
$apiKey = $client->apiKeys()->create(
    'my-app-key',                           // name
    [                                       // permissions
        ['action' => 'read', 'scope' => 'database', 'database' => 'mydb']
    ],
    '2025-12-31T23:59:59Z'                 // expires_at (optional)
);
echo "API Key: " . $apiKey['key'] . "\n";   // Store this securely!

// Get API key details
$key = $client->apiKeys()->get('key_id');

// Regenerate an API key
$newKey = $client->apiKeys()->regenerate('key_id');

// Delete an API key
$client->apiKeys()->delete('key_id');

Advanced Features

$client->vector()

Vector Search & AI
$client->useDatabase('mydb');

// Create a vector index
$index = $client->vector()->createIndex(
    'products',                 // collection
    'product_embeddings',       // name
    'embedding',                // field
    1536,                       // dimensions
    'cosine',                   // metric: cosine, euclidean, dot_product
    []                          // options (optional)
);

// Search by vector (semantic search)
$embedding = getEmbedding("wireless headphones");  // Your embedding function
$results = $client->vector()->search(
    'products',                 // collection
    $embedding,                 // vector
    10,                         // limit
    ['category' => 'electronics']  // filter (optional)
);

foreach ($results as $result) {
    echo $result['doc']['name'] . " - Score: " . $result['score'] . "\n";
}

// Search by existing document (find similar)
$similar = $client->vector()->searchByDocument(
    'products',                 // collection
    'product-123',              // doc_key
    'embedding',                // field
    5,                          // limit
    null                        // filter (optional)
);

// Quantize index (reduce memory usage)
$client->vector()->quantize('products', 'product_embeddings', 'binary');

// Dequantize (restore full precision)
$client->vector()->dequantize('products', 'product_embeddings');

// Get index info
$info = $client->vector()->getIndexInfo('products', 'product_embeddings');

// List vector indexes
$indexes = $client->vector()->listIndexes('products');

// Delete index
$client->vector()->deleteIndex('products', 'product_embeddings');

$client->geo()

Geospatial Queries
$client->useDatabase('mydb');

// Create a geo index
$client->geo()->createIndex(
    'stores',                   // collection
    'location_idx',             // name
    ['location'],               // fields containing [lat, lon] or GeoJSON
    true                        // geoJson (optional: true if using GeoJSON format)
);

// Find nearby locations (radius search)
$nearby = $client->geo()->near(
    'stores',                   // collection
    48.8566,                    // latitude
    2.3522,                     // longitude
    5000,                       // radius (meters)
    20                          // limit (optional)
);

foreach ($nearby as $result) {
    echo $result['doc']['name'] . " - " . $result['distance'] . "m away\n";
}

// 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]]]
];
$within = $client->geo()->within('stores', $polygon);

// Find intersecting geometries
$intersects = $client->geo()->intersects('zones', $polygon);

// Calculate distance between two points
$distance = $client->geo()->distance(48.8566, 2.3522, 51.5074, -0.1278);
echo "Paris to London: " . ($distance / 1000) . "km\n";

// List geo indexes
$indexes = $client->geo()->listIndexes('stores');

// Delete index
$client->geo()->deleteIndex('stores', 'location_idx');

$client->ttl()

Time-To-Live Indexes
$client->useDatabase('mydb');

// Create TTL index (auto-expire documents)
$client->ttl()->createIndex(
    'sessions',                 // collection
    'session_ttl',              // name
    'created_at',               // field (DateTime field to check)
    3600                        // expire_after_seconds (1 hour)
);

// Update expiration time
$client->ttl()->updateExpiration('sessions', 'session_ttl', 7200);  // 2 hours

// Get index info
$info = $client->ttl()->getIndexInfo('sessions', 'session_ttl');
echo "Expires after: " . $info['expire_after_seconds'] . "s\n";

// Manually trigger cleanup (normally runs automatically)
$result = $client->ttl()->runCleanup('sessions');
echo "Deleted " . $result['deleted'] . " expired documents\n";

// List TTL indexes
$indexes = $client->ttl()->listIndexes('sessions');

// Delete TTL index
$client->ttl()->deleteIndex('sessions', 'session_ttl');

$client->columnar()

Columnar/Analytics Storage
$client->useDatabase('mydb');

// Create a columnar table (optimized for analytics)
$table = $client->columnar()->create('metrics', [
    ['name' => 'timestamp', 'type' => 'datetime'],
    ['name' => 'metric_name', 'type' => 'string'],
    ['name' => 'value', 'type' => 'float'],
    ['name' => 'tags', 'type' => 'string']
]);

// Insert rows (batch insert is efficient)
$client->columnar()->insert('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
$results = $client->columnar()->query(
    'metrics',
    'SELECT * FROM metrics WHERE value > @min ORDER BY timestamp DESC LIMIT 100',
    ['min' => 40.0]
);

// Aggregation
$agg = $client->columnar()->aggregate('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
$stats = $client->columnar()->stats('metrics');
echo "Row count: " . $stats['row_count'] . ", Size: " . $stats['size_bytes'] . "\n";

// Add a column
$client->columnar()->addColumn('metrics', 'host', 'string', 'unknown');

// Drop a column
$client->columnar()->dropColumn('metrics', 'host');

// Create index on columnar table
$client->columnar()->createIndex('metrics', 'idx_timestamp', 'timestamp', 'btree');

// List indexes
$indexes = $client->columnar()->listIndexes('metrics');

// Delete index
$client->columnar()->deleteIndex('metrics', 'idx_timestamp');

// List all columnar tables
$tables = $client->columnar()->list();

// Get table info
$table = $client->columnar()->get('metrics');

// Delete table
$client->columnar()->delete('metrics');

$client->cluster()

Cluster Management
// Get cluster status
$status = $client->cluster()->status();
echo "Mode: " . $status['mode'] . "\n";    // standalone, cluster
echo "Nodes: " . $status['node_count'] . "\n";

// Get detailed cluster info
$info = $client->cluster()->info();

// Get all nodes
$nodes = $client->cluster()->getNodes();
foreach ($nodes as $node) {
    echo $node['id'] . ": " . $node['address'] . " (" . $node['status'] . ")\n";
}

// Get shard distribution
$shards = $client->cluster()->getShards();

// Remove a node from cluster
$client->cluster()->removeNode('node-id-to-remove');

// Trigger data rebalancing
$client->cluster()->rebalance();

// Cleanup orphaned data
$client->cluster()->cleanup();

// Reshard cluster
$client->cluster()->reshard(16);  // new number of shards

$client->collectionsOps()

Advanced Collection Operations
$client->useDatabase('mydb');

// Truncate collection (delete all documents)
$client->collectionsOps()->truncate('logs');

// Compact collection (reclaim disk space)
$client->collectionsOps()->compact('users');

// Repair collection (fix inconsistencies)
$result = $client->collectionsOps()->repair('orders');

// Get collection statistics
$stats = $client->collectionsOps()->stats('users');

// Prune old documents
$result = $client->collectionsOps()->prune('logs', '2024-01-01T00:00:00Z', 'created_at');

// Recount documents
$result = $client->collectionsOps()->recount('users');

// Set JSON schema validation
$client->collectionsOps()->setSchema('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
$schema = $client->collectionsOps()->getSchema('users');

// Remove schema validation
$client->collectionsOps()->deleteSchema('users');

// Export collection
$data = $client->collectionsOps()->export('users', 'json');  // json, csv, msgpack

// Import data
$client->collectionsOps()->import('users_backup', $data, 'json');

// Get sharding configuration
$sharding = $client->collectionsOps()->getSharding('orders');

// Configure sharding
$client->collectionsOps()->setSharding('orders', [
    'num_shards' => 8,
    'shard_key' => 'user_id'
]);

$client->indexesOps()

Advanced Index Operations
$client->useDatabase('mydb');

// Rebuild a specific index
$client->indexesOps()->rebuild('users', 'idx_email');

// Rebuild all indexes on a collection
$client->indexesOps()->rebuildAll('users');

// Hybrid search (combine vector + keyword search)
$results = $client->indexesOps()->hybridSearch('products', [
    'vector' => $embedding,
    'text' => 'wireless headphones',
    'vector_field' => 'embedding',
    'text_field' => 'description',
    'limit' => 10
]);

// Analyze index performance
$analysis = $client->indexesOps()->analyze('users', 'idx_email');

// Get index usage statistics
$stats = $client->indexesOps()->getUsageStats('users');

$client->env()

Environment Variables
$client->useDatabase('mydb');

// List environment variables (for Lua scripts)
$vars = $client->env()->list();

// Get a specific variable
$value = $client->env()->get('API_KEY');

// Set an environment variable
$client->env()->set('API_KEY', 'sk-xxx-your-api-key');
$client->env()->set('WEBHOOK_URL', 'https://example.com/webhook');

// Set multiple variables at once
$client->env()->setBulk([
    'API_KEY' => 'sk-xxx-key',
    'API_SECRET' => 'secret123',
    'DEBUG' => 'false'
]);

// Delete an environment variable
$client->env()->delete('OLD_VAR');

Error Handling

<?php
use SoliDB\Client;
use SoliDB\Exception\DriverException;

try {
    $client = new Client('127.0.0.1', 6745);
    $client->connect();
    $client->auth('mydb', 'user', 'password');

    $doc = $client->get('mydb', 'users', 'nonexistent-key');

} catch (DriverException $e) {
    // All SoliDB errors extend DriverException
    echo "Error: " . $e->getMessage() . "\n";
    echo "Error Type: " . $e->getErrorType() . "\n";

    switch ($e->getErrorType()) {
        case 'connection_error':
            // Network/connection issues
            echo "Connection failed - check server availability\n";
            break;
        case 'server_error':
            // Server-side errors (not found, validation, permissions)
            echo "Server error - check request parameters\n";
            break;
        case 'serialization_error':
            // Protocol/serialization errors
            echo "Protocol error - possible version mismatch\n";
            break;
        case 'protocol_error':
            // Response too large, invalid format
            echo "Protocol error - response format issue\n";
            break;
    }

} finally {
    if (isset($client)) {
        $client->close();
    }
}

connection_error

Network failures, connection refused, timeouts, disconnections

server_error

Document not found, permission denied, validation errors

serialization_error

MessagePack encoding/decoding failures

protocol_error

Response too large, invalid message format