haematite-cli 0.21.1

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! The served vocabulary and reserved-stage refusals share one table.

use serde::Serialize;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Role {
    Source,
    Search,
    Bound,
    Order,
    Count,
    Aggregate,
    Shape,
    Group,
    Target,
    Measure,
    StageNotYet,
    JqKeyword,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Status {
    Served,
    NotYet,
}

#[derive(Debug, Serialize)]
pub struct Word {
    pub name: &'static str,
    pub role: Role,
    pub status: Status,
    pub arguments: &'static [&'static str],
    pub meaning: &'static str,
    #[serde(skip)]
    pub refusal: Option<&'static str>,
}

const fn served(
    name: &'static str,
    role: Role,
    arguments: &'static [&'static str],
    meaning: &'static str,
) -> Word {
    Word {
        name,
        role,
        status: Status::Served,
        arguments,
        meaning,
        refusal: None,
    }
}

const fn reserved_stage(
    name: &'static str,
    arguments: &'static [&'static str],
    meaning: &'static str,
    refusal: &'static str,
) -> Word {
    Word {
        name,
        role: Role::StageNotYet,
        status: Status::NotYet,
        arguments,
        meaning,
        refusal: Some(refusal),
    }
}

pub const WORDS: &[Word] = &[
    served(
        "collection",
        Role::Source,
        &["name: a nonempty collection name in double quotes"],
        "Read records from the named collection, including names that cannot be written bare.",
    ),
    served(
        "and",
        Role::JqKeyword,
        &[
            "source: join field equalities inside collection arguments",
            "jq: combine two boolean expressions",
        ],
        "Join collection equalities or combine boolean expressions inside quoted jq.",
    ),
    served(
        "similar",
        Role::Search,
        &[
            "field: a top-level vector field with a leading dot",
            "question: double-quoted text or record(\"id\")",
            "measure: cosine, l2 or dot",
        ],
        "Search by vector similarity first after its source or later over the records that reached it.",
    ),
    served(
        "record",
        Role::Target,
        &["id: a record id in double quotes"],
        "Use the stored vector of the named record as the similarity question.",
    ),
    served(
        "limit",
        Role::Bound,
        &["n: a whole number from 1 to 4294967295"],
        "Stop the upstream after at most n records or values at this point in the pipeline.",
    ),
    served(
        "sort",
        Role::Order,
        &[
            "field: a top-level field with a leading dot",
            "direction: optional desc after a semicolon",
        ],
        "Order the whole upstream by a field before the quoted jq stage.",
    ),
    served(
        "desc",
        Role::Order,
        &[],
        "Order the sort field from greatest to least instead of ascending order.",
    ),
    served(
        "count",
        Role::Count,
        &[],
        "Consume the whole upstream and answer its size as the last stage.",
    ),
    served(
        "cosine",
        Role::Measure,
        &[],
        "Compare vectors by their cosine similarity.",
    ),
    served(
        "l2",
        Role::Measure,
        &[],
        "Compare vectors by their Euclidean distance.",
    ),
    served(
        "dot",
        Role::Measure,
        &[],
        "Compare vectors by their dot product.",
    ),
    served(
        "match",
        Role::Source,
        &[
            "nodes: (binding:collection) or up to eight labelled nodes joined by directed relations",
            "binding: distinct names matching [a-z_][a-z0-9_]*",
            "relation: -[:kind]-> or <-[:kind]- at each of up to seven hops",
        ],
        "Match a labelled collection or a fixed chain of directed relations first or immediately after its collection.",
    ),
    served(
        "rg",
        Role::Search,
        &[
            "field: optional top-level field with a leading dot",
            "pattern: a regular expression in double quotes",
            "flags: optional quoted -i, -s, -S, -w, -x, -F, -v and -U switches and their long forms; --no-ignore-case resets case sensitivity; -U dot matches newlines",
        ],
        "Keep whole records in order when their compact JSON or named field matches the pattern.",
    ),
    served(
        "grep",
        Role::Search,
        &[
            "field: optional top-level field with a leading dot",
            "pattern: a regular expression in double quotes",
            "flags: optional quoted -i, -s, -S, -w, -x, -F, -v and -U switches and their long forms; --no-ignore-case resets case sensitivity; -U dot matches newlines",
        ],
        "Alias of rg: keep whole records in order when their compact JSON or named field matches the pattern.",
    ),
    served(
        "rerank",
        Role::Order,
        &[
            "field: a top-level vector field with a leading dot",
            "question: double-quoted text or record(\"id\")",
            "measure: cosine, l2 or dot",
        ],
        "Drain the bounded upstream, reorder by vector score, replace scores and count unscored records last; no continuation.",
    ),
    reserved_stage(
        "sort_by",
        &["expression: a sort key"],
        "Reserve an unquoted sort_by stage that is not available yet.",
        "a `sort_by` stage",
    ),
    served(
        "window",
        Role::Bound,
        &[
            "from: an included unsigned version or quoted RFC 3339 instant",
            "to: an excluded bound of the same form, or $now",
        ],
        "Keep records by their revision append version, never by a time inside the record.",
    ),
    served(
        "group_by",
        Role::Group,
        &["field: a top-level field with a leading dot"],
        "Drain records before jq into key, count and values groups in jq value order; missing fields group under null and no continuation is available.",
    ),
    served(
        "percentile",
        Role::Aggregate,
        &[
            "field: a top-level numeric field with a leading dot",
            "percentile: a number from 0 to 100 inclusive",
        ],
        "Drain records before jq into one nearest-rank JSON number; empty numeric input gives null, zero selects the minimum, non-numbers are counted as skipped, and no continuation is available.",
    ),
    served(
        "{",
        Role::Shape,
        &[
            "fields: bare names with nested selection sets",
            "source: one or several collection roots in a first-stage shape",
        ],
        "Keep selected fields in record order; a first-stage shape reads collections in root order before worker Input.",
    ),
    served(
        "as",
        Role::JqKeyword,
        &["pattern: bind a jq value to variables"],
        "Bind values to variables within a quoted jq expression.",
    ),
    served(
        "def",
        Role::JqKeyword,
        &["name and parameters: the filter definition"],
        "Define a reusable filter inside quoted jq.",
    ),
    served(
        "if",
        Role::JqKeyword,
        &["condition: the tested expression"],
        "Start a conditional expression inside quoted jq.",
    ),
    served(
        "then",
        Role::JqKeyword,
        &["expression: the branch to evaluate"],
        "Introduce a conditional branch inside quoted jq.",
    ),
    served(
        "elif",
        Role::JqKeyword,
        &["condition: an additional test"],
        "Add another conditional test inside quoted jq.",
    ),
    served(
        "else",
        Role::JqKeyword,
        &["expression: the alternative branch"],
        "Introduce the alternative branch inside quoted jq.",
    ),
    served(
        "end",
        Role::JqKeyword,
        &[],
        "Close a conditional expression inside quoted jq.",
    ),
    served(
        "or",
        Role::JqKeyword,
        &["expressions: the boolean operands"],
        "Combine boolean expressions by disjunction inside quoted jq.",
    ),
    served(
        "reduce",
        Role::JqKeyword,
        &["generator and accumulator: the reduction expressions"],
        "Fold generated values into an accumulator inside quoted jq.",
    ),
    served(
        "foreach",
        Role::JqKeyword,
        &["generator, accumulator and output: the iteration expressions"],
        "Fold generated values while emitting intermediate results inside quoted jq.",
    ),
    served(
        "in",
        Role::JqKeyword,
        &["container: the object or array to inspect"],
        "Test whether the current key belongs to a container inside quoted jq.",
    ),
    served(
        "try",
        Role::JqKeyword,
        &["expression: the expression whose errors are caught"],
        "Evaluate an expression with error handling inside quoted jq.",
    ),
    served(
        "catch",
        Role::JqKeyword,
        &["expression: the error handler"],
        "Handle an error from a try expression inside quoted jq.",
    ),
    served(
        "label",
        Role::JqKeyword,
        &["name: a label variable"],
        "Name a control-flow boundary inside quoted jq.",
    ),
    served(
        "break",
        Role::JqKeyword,
        &["name: the label to exit"],
        "Exit a named control-flow boundary inside quoted jq.",
    ),
    served(
        "module",
        Role::JqKeyword,
        &["metadata: a JSON object"],
        "Declare module metadata inside a jq program.",
    ),
    served(
        "import",
        Role::JqKeyword,
        &["path and alias: the module to load"],
        "Import a module under an alias inside a jq program.",
    ),
    served(
        "include",
        Role::JqKeyword,
        &["path: the module to load"],
        "Include a module directly inside a jq program.",
    ),
    served(
        "true",
        Role::JqKeyword,
        &[],
        "Produce the boolean true inside quoted jq or a collection argument.",
    ),
    served(
        "false",
        Role::JqKeyword,
        &[],
        "Produce the boolean false inside quoted jq or a collection argument.",
    ),
    served(
        "null",
        Role::JqKeyword,
        &[],
        "Produce the null value inside quoted jq or a collection argument.",
    ),
];

pub(super) fn reserved(name: &str) -> Option<&'static str> {
    WORDS
        .iter()
        .find(|word| word.name == name && word.status == Status::NotYet)
        .and_then(|word| word.refusal)
}