Skip to content

Qirava DMS docs

Core concepts

The DMS is built on one small idea: everything that runs is a registered function, and the only way to run one is execute(). On top of that sit a function registry (who can run what, and under whose ownership), a bounded worker executor that schedules and resource-governs every call, and a worker layer that turns a protocol request into an ordered list of function calls. Databases, governance, the query engine, even the admin UI are all just function groups wired into the same runtime. This page gives you the mental model the rest of the section builds on.

The four moving parts#

There are exactly four concepts to hold in your head. Once you have them, every other page in this section is a detail of one of them.

The engine (execute)
FunctionRuntime::execute(function_key, input, fifo, execution_key) is the one and only way to run a registered function — from an inbound request, from a scheduler, or re-entrantly from inside another function. It resolves the key, admits the call against the resource budget, schedules it on the bounded worker pool, and returns a FunctionCall you .wait() on for a FunctionResponse.
The function registry
Every callable is registered with a string key, a scope (Public / InternalGlobal / InternalScoped / InternalPrivate), an origin (Builtin or UserDefined), a declared resource budget, and a fifo flag. The registry is what makes access deny-by-default: only Public functions can be reached by execute(); internal helpers are reachable only from inside a function via ctx.call/chain/all.
The workers
A Worker is an OUTWARD layer over the engine: a bound address + domains + the app it serves + its routes and groups. It does not store data or run logic; it only describes which functions to call for a request and in what order (before -> handle -> after), and it enforces which functions an app is allowed to bind.
Config-as-data
Workers, routes, groups, users, API keys, sessions, scheduled jobs, and pages live as ROWS in _sys_* catalog tables, not in code. A running node loads its routes from _sys_routes, its workers from _sys_workers, and so on — so the system is reconfigured by writing data, governed by the same RBAC as any other write.

Everything is a function group#

The DMS ships its capabilities as function groups, each in its own folder under src/functions/ with a schemas.rs (declares keys + specs) and a mod.rs (registers the bodies). register_all wires every group into one runtime. The built-in groups are: planner, read, write, search, graph, vector, inline, plus auth (the RBAC/governance before-functions), db (the engine/QQL/WAL/tables), and docs (the documentation site app).

  • planner — parse/normalize a query and plan read/write access paths (the only read/mutate gate).
  • read / write — the keyed, filtered, sorted, joined, aggregated reads and the insert/update/upsert/delete/move/upgrade writes.
  • search / graph / vector — full-text, graph traversal, and vector KNN access functions.
  • inline — composite roots that fan a read into a search or join into a vector lookup.
  • auth — authentication + RBAC + governance before-functions (sessions, HMAC-signed keys, role hierarchy).
  • db — the storage engine, QQL parser/planner, write-ahead log, and table registry.
rust
// src/functions/mod.rs — every group wired into one runtime
pub fn register_all(builder: &mut FunctionRuntimeBuilder) -> Result<(), RegistryError> {
    planner::register(builder)?;
    read::register(builder)?;
    write::register(builder)?;
    search::register(builder)?;
    graph::register(builder)?;
    vector::register(builder)?;
    inline::register(builder)?;
    Ok(())
}

Building a runtime#

In production you build the runtime by auto-scanning the host device for CPU/RAM/storage and applying the default 80% safety budget. That is exactly runtime(ExecConfig::auto()). You can also build one with an explicit ExecConfig for tests or constrained deployments.

rust
// Production path: scan device, apply 80% budget, enforce it.
let runtime = qdms::functions::runtime_auto()?;            // = runtime(ExecConfig::auto())

// Explicit config (e.g. tests): 2 worker threads, hard 1 KiB memory ceiling.
let config = ExecConfig { worker_threads: Some(2), max_memory_bytes: Some(1024),
                          ..ExecConfig::default_for(2) };
let runtime = qdms::functions::runtime(config)?;

The uniform response envelope#

Whatever route or protocol you hit, a worker response uses ONE JSON shape: error and data are always present (one is null) plus a root meta block. This is the contract every client codes against.

Request

// success and error are mirror images — error XOR data is null

Response

// success
{"error":null,"data":{...},"root":{"took_us":123,"request_id":"..."}}

// failure
{"error":{"code":"forbidden","message":"..."},"data":null,"root":{"took_us":0,"request_id":"..."}}

The stable string codes and their HTTP statuses come straight from the ResponseCode mapping: ok=200, workers_busy=503, not_found=404, forbidden=403, bad_request=400, rate_limited=429 (resource limit), function_failed=422, internal=500.