Lua Scripting

Extend SoliDB with custom API endpoints written in Lua. Execute logic directly on the database server for maximum performance.

Overview

SoliDB allows you to embed Lua scripts that act as custom API endpoints. These scripts have direct access to internal database objects, allowing you to perform complex data manipulations, validations, or multi-step transactions in a single HTTP request.

Performance

Zero network latency between your logic and your data. Ideal for complex aggregates.

Simplicity

Write clean, synchronous Lua code. No async/await hell.

Custom APIs

Expose exactly the endpoints your frontend needs, hiding complex queries.

Security Sandbox

Scripts run in a strictly sandboxed isolation environment to ensure server stability and security.

Allowed Modules

  • string
  • table
  • math
  • utf8
  • bit

Disabled Modules

  • os (Access system commands)
  • io (File system access)
  • debug (Reflection tools)
  • package (Module loading)

Attempts to access disabled modules or perform unauthorized system calls will result in a runtime error.

Management API

Before you can execute a script, you must register it via the management API.

Example Request POST /_api/database/:db/scripts
curl -X POST http://localhost:6745/_api/database/my_app/scripts \
-H "Authorization: Bearer $TOKEN" \
-d '{ "name": "Greeting API", "path": "greet", # Result: /api/custom/my_app/greet "methods": ["GET"], "code": "return { greeting = \"Hello!\" }" }'

Endpoints

Method Endpoint Description
POST /_api/scripts Create a new script
GET /_api/scripts List all scripts
PUT /_api/scripts/:id Update a script
DELETE /_api/scripts/:id Delete a script

Lua API Reference

solidb

The main entry point for database operations.

  • db
    The current database object.
  • solidb.log(message)
    Log a message to the server console.
  • solidb.fetch(url, options) -> Response
    Make an HTTP request. Options: { method="GET", headers={}, body="" }. Returns { status, body, headers, ok }.

Database

  • db:collection(name) -> Collection
    Get a handle to a collection.
  • db:query(query, params) -> list<document>
    Execute an SDBQL query.
  • db:transaction(callback) -> any
    Execute operations atomically within a transaction. The callback receives a tx context. Auto-commits on success, auto-rollbacks on error.

Transactions

Inside a db:transaction() callback, use the tx object for atomic operations.

  • tx:collection(name) -> TxCollection
    Get a transactional collection handle.
  • txCol:insert(doc) -> document
  • txCol:update(key, doc) -> document
  • txCol:delete(key) -> bool
  • txCol:get(key) -> document | nil

Collection

  • col:get(key) -> document | nil
  • col:insert(doc) -> document
  • col:update(key, doc) -> document
  • col:delete(key) -> bool
  • col:count() -> int

String Extensions

Extensions to the standard Lua string library.

  • string.regex(subject, pattern) -> bool
    Check if a string matches a regex pattern. Also available as subject:regex(pattern).
    Example: string.regex("hello", "^he") -> true
  • string.regex_replace(subject, pattern, replacement) -> string
    Replace all occurrences of a regex pattern. Also available as subject:regex_replace(pattern, repl).
    Example: string.regex_replace("a1b2", "\\d", "#") -> "a#b#"

Crypto Functions

Cryptographic utilities exposed via the crypto global.

  • crypto.md5(data) -> string (hex)
  • crypto.sha256(data) -> string (hex)
  • crypto.sha512(data) -> string (hex)
  • crypto.hmac_sha256(key, data) -> string (hex)
  • crypto.hmac_sha512(key, data) -> string (hex)
  • crypto.base64_encode(data) -> string
  • crypto.base64_decode(data) -> string
  • crypto.base32_encode(data) -> string
  • crypto.base32_decode(data) -> string
  • crypto.uuid() -> string (v4)
  • crypto.uuid_v7() -> string (v7)
  • crypto.random_bytes(len) -> string (bytes)
  • crypto.curve25519(secret, public) -> string (bytes)
    Calculate X25519 shared secret or public key.
    crypto.curve25519(secret, "\9") -> public key
  • crypto.jwt_encode(claims, secret) -> string
  • crypto.jwt_decode(token, secret) -> table
  • crypto.hash_password(pwd) -> string
    Hash password using Argon2 (Async/Non-blocking).
  • crypto.verify_password(hash, pwd) -> bool
    Verify password (Async/Non-blocking).

Time Functions

Time utilities via time global.

  • time.now() -> float (seconds)
  • time.now_ms() -> int (milliseconds)
  • time.iso() -> string (ISO 8601)
  • time.sleep(ms) -> nil
    Suspend execution for N ms (Non-blocking).
  • time.format(ts, format) -> string
    Format timestamp (e.g. "%Y-%m-%d %H:%M:%S").
  • time.parse(iso) -> float (seconds)
  • time.add(ts, val, unit) -> float
    Add unit ("ms", "s", "m", "h", "d") to timestamp.
  • time.subtract(ts, val, unit) -> float
    Subtract unit from timestamp.

request

Contains information about the incoming HTTP request.

  • request.method -> string ("GET", "POST", etc.)
  • request.path -> string
  • request.query -> table (URL query parameters)
  • request.params -> table (URL path parameters, e.g. :id)
  • request.body -> table | nil (Parsed JSON body)
  • request.headers -> table

Examples

1. Hello World with Query Params

-- Returns: { "greeting": "Hello, Alice!" }
local name = request.query.name or "World"
return {
    greeting = "Hello, " .. name .. "!"
}

2. CRUD API for "Users"

Handle both GET (list) and POST (create) in a single script.

-- Global 'db' object is pre-connected to your database
local users = db:collection("users")

if request.method == "POST" then
    -- Validate input
    if not request.body.email then
        return { error = "Email required" }
    end

    -- Create user
    local doc = users:insert(request.body)
    return { status = "created", user = doc }

else
    -- List users (supports simple pagination via query params)
    local limit = tonumber(request.query.limit) or 10
    
    -- Execute SDBQL query for better performance
    local all_users = db:query("FOR u IN users RETURN u")
    
    return { 
        data = all_users,
        count = users:count() 
    }
end

3. SDBQL Query with Parameters

Execute complex queries safely using bind parameters.

-- Get users older than 'min_age' query parameter
local min_age = tonumber(request.query.age) or 18

-- Execute SDBQL query with bind variables (safe against injection)
local results = db:query(
    "FOR u IN users FILTER u.age >= @age RETURN u",
    { age = min_age }
)

return {
    count = #results,
    users = results
}

4. URL Path Parameters

Handle dynamic paths like customers/:id.

-- Script path defined as: customers/:id
local id = request.params.id

if not id then
    return { error = "ID required" }
end

-- Look up customer by ID
local customers = db:collection("customers")
local customer = customers:get(id)

return customer or { error = "Customer not found" }

5. HTTP Request (Webhook)

Send data to an external service using solidb.fetch. This function is asynchronous and non-blocking — execution suspends until the response arrives, freeing up server resources.

-- Post data to an external API
local res = solidb.fetch("https://api.example.com/webhook", {
    method = "POST",
    headers = {
        ["Content-Type"] = "application/json",
        ["Authorization"] = "Bearer secret"
    },
    body = '{ "event": "user_created" }'
})

return {
    status = res.status,
    upstream_response = res.body
}

6. End-to-End Encryption with Curve25519

Establish a shared secret between two parties using Elliptic Curve Diffie-Hellman.

-- 1. Alice generates her keypair
 local alice_secret = crypto.random_bytes(32)
 local alice_public = crypto.curve25519(alice_secret, "\9") -- Generate public key
 
 -- 2. Bob generates his keypair
 local bob_secret = crypto.random_bytes(32)
 local bob_public = crypto.curve25519(bob_secret, "\9")
 
 -- 3. Calculate shared secret (Alice uses Bob's public key, Bob uses Alice's)
 local shared_secret_alice = crypto.curve25519(alice_secret, bob_public)
 local shared_secret_bob = crypto.curve25519(bob_secret, alice_public)
 
 -- The shared secrets are identical
 local keys_match = (shared_secret_alice == shared_secret_bob)
 
 -- Derive a session key (e.g. for HMAC or encryption)
 local session_key = crypto.sha256(shared_secret_alice)
 
 return {
     alice_public_hex = crypto.hex_encode(alice_public),
     bob_public_hex = crypto.hex_encode(bob_public),
     keys_match = keys_match,
     session_key_hex = session_key
 }

7. Atomic Transactions

Perform multiple operations atomically with auto-commit on success or auto-rollback on error.

-- Transfer credits between two accounts atomically
db:transaction(function(tx)
    local accounts = tx:collection("accounts")
    
    -- Debit source account
    local source = accounts:get("alice")
    accounts:update("alice", { balance = source.balance - 100 })
    
    -- Credit destination account
    local dest = accounts:get("bob")
    accounts:update("bob", { balance = dest.balance + 100 })
    
    -- If any error occurs, all changes are rolled back
end)

return { status = "Transfer complete!" }