rust-store

Embed the core in a Rust service

The problem

You have a Rust service and you want the same query dialect, permission model and computed-column behaviour that the nodejs-store / py-store hosts already use. You do not want to re-implement GQL parsing, permission checks or SQL translation, and you do not want to hand the engine a database connection — your service already owns its drivers and its connection pool.

Why rust-store

rust-store-core is a plain Rust library that holds all of the pure logic: schema registry, GQL parsing, permission checks, computed columns, command planning and dialect translation. It performs no IO — it never opens a connection, never reads the clock and never generates a random id. Every plan method returns command JSON that you execute.

The crates are publish = false, so you depend on the core by path rather than through crates.io.

Walkthrough

Add the core as a path dependency:

[dependencies]
rust-store-core = { path = "path/to/rust-store/core" }
serde_json = "1"

Register schemas, plan a read, then translate the command for a relational backend:

use serde_json::{json, Map};

use rust_store_core::command::{plan_insert, plan_query};
use rust_store_core::dialect::{translate, Backend};
use rust_store_core::permission::Context;
use rust_store_core::schema::Registry;

fn main() -> Result<(), String> {
    // 1) Register schemas. The core derives the `<Name>Deleted` archive table for you,
    //    and validates the `(source, namespace, collection)` location triple.
    let mut registry = Registry::new();
    registry.register(&json!({
        "name": "Order",
        "collection": "orders",
        "timestamps": false,
        "fields": {
            "code":   { "type": "string" },
            "amount": { "type": "float" }
        },
        "relations": {}
    }))?;

    // 2) Plan a read. `plan_query` is a pure `gql -> plan` function: it does not touch a
    //    database. `ctx` is an explicit parameter.
    let mut params = Map::new();
    params.insert("c0".to_string(), json!({ "amount": { "$gte": 10 } }));

    let ctx = Context::system(); // internal call: permission engine passes everything
    let plan = plan_query(
        "Order($condition:@c0){ code, amount }",
        &params,
        &registry,
        Some(&ctx),
    )?;

    // `plan.commands` is MongoDB command JSON. Hand it to YOUR driver: the `source`,
    // `namespace` and `collection` fields tell you which connection to use.
    let cmd = plan.commands.first().ok_or("query planned no command")?;
    // let docs = my_driver.execute(cmd)?;

    // 3) The very same command translates to parameterized SQL via a pure function.
    //    The result is `{ backend, stmts, warnings, unsupported }`.
    let sql = translate(Backend::Postgres, cmd, &registry)?;
    let _ = sql;

    // 4) Writes need a clock and an id source. The core has neither, so the host supplies
    //    both — this is what makes results deterministic and cross-language reproducible.
    let now_ms: i64 = 1_760_000_000_000; // your host's clock
    let new_id = "ord_01HZ";             // your host's id generator
    let write = plan_insert(
        "Order",
        &registry,
        Some(&ctx),
        &json!({ "code": "A-1", "amount": 12.5 }),
        now_ms,
        new_id,
        None, // sync computed-column callbacks, if any
    )?;
    // write["command"] = insertOne command; write["returns"] = the doc after defaults/computes.

    Ok(())
}

The binding name for the same translation entry point is dialectTranslate / dialect_translate; in Rust it is exported as rust_store_core::dialect::translate.

Pitfalls

See also