Adventure Labs Logo
Scout Docs

Extending the Schema

Reading and writing tables and RPCs a deployment adds on top of Scout's

Extending the schema

Scout models its own tables as Rust structs, but the transport underneath is generic. A deployment that adds tables or PostgREST functions defines plain serde structs and reaches them through ScoutDbClient, which ScoutClient::get_db_client exposes.

Remote tables

query, query_one, insert, insert_bulk, upsert_bulk, update and delete are generic over any Serialize/Deserialize type, so an added table needs no changes to the crate.

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct FieldNote {
    id: Option<i64>,
    herd_id: i64,
    body: String,
}

let db = client.get_db_client()?;
let notes: Vec<FieldNote> = db
    .query(|rest| {
        rest.from("field_notes")
            .select("*")
            .eq("herd_id", herd_id.to_string())
    })
    .await?;

Remote RPCs

rpc calls any function the deployment exposes and deserializes the result; rpc_execute discards it, which is what a function returning void needs. Both name the function in their error and report the message from a PostgREST error body where there is one.

let notes: Vec<FieldNote> = db
    .rpc(
        "field_notes_for_herd",
        &serde_json::json!({ "herd_id_caller": herd_id }),
    )
    .await?;

db.rpc_execute("archive_field_notes", &serde_json::json!({ "herd_id": herd_id }))
    .await?;

What is not extensible

The sync engine's embedded database holds Scout's own models only. Its model set is fixed at the versions Scout ships so that a database written by an older build still opens, and flush pushes a fixed tree to the remote database: sessions, then their connectivity, events, operators and artifacts, then tags and sensor poses.

A deployment that needs its own local persistence keeps it outside the sync engine and pushes it through ScoutDbClient.

On this page